From aaaa35d648b77decc02a0027a387406c7cca97a5 Mon Sep 17 00:00:00 2001 From: ChangAo Chen Date: Tue, 23 Jun 2026 11:12:23 +0800 Subject: [PATCH v3 1/2] Handle concurrent drop when doing whole database vacuum. When doing a whole database vacuum, we scan pg_class to construct a list of vacuumable tables. For each vacuumable table, we call vacuum_is_permitted_for_relation() to check permissions. If a concurrent drop happens, the pg_class_aclcheck() might report an error because of failing to search the syscache. To fix it, we use pg_class_aclcheck_ext() to detect the concurrent drop and report a warning instead. --- src/backend/commands/vacuum.c | 36 ++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index a4abb29cf64..2217188d86e 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -715,12 +715,18 @@ 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. + * + * Note that the relation might not be locked, so it can be dropped concurrently. + * This can happen when doing a whole database vacuum or analyze in + * get_all_vacuum_rels(). We issue a WARNING log message and return false in + * this case. */ bool vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, uint32 options) { char *relname; + bool is_missing = false; Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0); @@ -729,20 +735,28 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, * following are true: * - the role owns the current database and the relation is not shared * - the role has the MAINTAIN privilege on the relation + * + * Note: use pg_class_aclcheck_ext() to detect a concurrent drop. *---------- */ 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, &is_missing) == ACLCHECK_OK) return true; relname = NameStr(reltuple->relname); if ((options & VACOPT_VACUUM) != 0) { - ereport(WARNING, - (errmsg("permission denied to vacuum \"%s\", skipping it", - relname))); + if (is_missing) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping vacuum of \"%s\" --- relation no longer exists", + relname))); + else + ereport(WARNING, + (errmsg("permission denied to vacuum \"%s\", skipping it", + relname))); /* * For VACUUM ANALYZE, both logs could show up, but just generate @@ -753,9 +767,17 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple, } if ((options & VACOPT_ANALYZE) != 0) - ereport(WARNING, - (errmsg("permission denied to analyze \"%s\", skipping it", - relname))); + { + if (is_missing) + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("skipping analyze of \"%s\" --- relation no longer exists", + relname))); + else + ereport(WARNING, + (errmsg("permission denied to analyze \"%s\", skipping it", + relname))); + } return false; } -- 2.34.1