From 824d5f6241ea7a0a85c9d2b3d27beb78e42a36ab Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 3 Aug 2026 16:02:07 -0500 Subject: [PATCH] Handle concurrently-dropped relations in database-wide VACUUM. When VACUUM or ANALYZE is run without a table list, we scan pg_class to build the list of relations to process, and we check the privileges on each relation we find. Since we don't take any locks on the relations at this point, it's possible for one to be concurrently dropped, in which case the privilege check fails with an ERROR such as the following: ERROR: relation with OID 16388 does not exist This unnecessarily aborts the entire command. To fix, use pg_class_aclcheck_ext() for the privilege check so that we can detect concurrently-dropped relations and silently skip them. There's no need to emit a WARNING for such relations because they weren't explicitly named, and a drop at this point is no different than one that happened before our pg_class scan began. Note that concurrent drops that occur later on are already handled gracefully by vacuum_open_relation(). The new missing_ok behavior is limited to get_all_vacuum_rels(). All other callers of vacuum_is_permitted_for_relation() should hold a lock on the relation that prevents it from being concurrently dropped, so this commit also adds an assertion to that effect. Oversight in commit a556549d7e. This is a bug fix and could be back-patched, but given the presumed rarity of the race condition and the lack of field reports, I'm not going to bother. Reported-by: ChangAo Chen Author: ChangAo Chen Co-authored-by: Nathan Bossart Reviewed-by: Kyotaro Horiguchi Reviewed-by: Surya Poondla Reviewed-by: Bharath Rupireddy Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/tencent_F9D483523BB0D082C2EFDA80142F192DBC07%40qq.com --- src/backend/commands/analyze.c | 3 ++- src/backend/commands/vacuum.c | 29 ++++++++++++++++++++++++----- src/include/commands/vacuum.h | 2 +- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index f66e80b757c..c28b9dae983 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -157,7 +157,8 @@ analyze_rel(Oid relid, RangeVar *relation, */ if (!vacuum_is_permitted_for_relation(RelationGetRelid(onerel), onerel->rd_rel, - params->options & ~VACOPT_VACUUM)) + params->options & ~VACOPT_VACUUM, + false)) { relation_close(onerel, ShareUpdateExclusiveLock); return; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 38539a6fd3d..31f9824899c 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -715,14 +715,21 @@ vacuum(List *relations, const VacuumParams *params, BufferAccessStrategy bstrate * If not, issue a WARNING log message and return false to let the caller * decide what to do with this relation. This routine is used to decide if a * relation can be processed for VACUUM or ANALYZE. + * + * If missing_ok is true, we silently return false if the relation is + * concurrently dropped. Callers without a lock on the relation must specify + * missing_ok; all others must hold at least AccessShareLock. */ bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, - uint32 options) + uint32 options, bool missing_ok) { char *relname; + bool is_missing = false; Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0); + Assert(missing_ok || + CheckRelationOidLockedByMe(relid, AccessShareLock, true)); /*---------- * A role has privileges to vacuum or analyze the relation if any of the @@ -733,9 +740,20 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, */ if ((object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) || - pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) == ACLCHECK_OK) + pg_class_aclcheck_ext(relid, GetUserId(), ACL_MAINTAIN, + missing_ok ? &is_missing : NULL) == ACLCHECK_OK) return true; + /* + * If the relation was concurrently dropped, nothing to do. Note that + * this is only reachable when the caller specified missing_ok. + */ + if (is_missing) + { + Assert(missing_ok); + return false; + } + relname = NameStr(reltuple->relname); if ((options & VACOPT_VACUUM) != 0) @@ -956,7 +974,7 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context, * Make a returnable VacuumRelation for this rel if the user has the * required privileges. */ - if (vacuum_is_permitted_for_relation(relid, classForm, options)) + if (vacuum_is_permitted_for_relation(relid, classForm, options, false)) { oldcontext = MemoryContextSwitchTo(vac_context); vacrels = lappend(vacrels, makeVacuumRelation(vrel->relation, @@ -1069,7 +1087,7 @@ get_all_vacuum_rels(MemoryContext vac_context, int options) continue; /* check permissions of relation */ - if (!vacuum_is_permitted_for_relation(relid, classForm, options)) + if (!vacuum_is_permitted_for_relation(relid, classForm, options, true)) continue; /* @@ -2115,7 +2133,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, */ if (!vacuum_is_permitted_for_relation(priv_relid, rel->rd_rel, - params.options & ~VACOPT_ANALYZE)) + params.options & ~VACOPT_ANALYZE, + false)) { relation_close(rel, lmode); PopActiveSnapshot(); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 956d9cea36d..e62f23748dc 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -389,7 +389,7 @@ extern bool vacuum_xid_failsafe_check(const struct VacuumCutoffs *cutoffs); extern void vac_update_datfrozenxid(void); extern void vacuum_delay_point(bool is_analyze); extern bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, - uint32 options); + uint32 options, bool missing_ok); extern Relation vacuum_open_relation(Oid relid, RangeVar *relation, uint32 options, bool verbose, LOCKMODE lmode); -- 2.47.3