From acfa320c1834ed0cd924fe8250d309905ae398f4 Mon Sep 17 00:00:00 2001 From: ChangAo Chen Date: Thu, 25 Jun 2026 13:28:14 +0800 Subject: [PATCH v4 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 | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index a4abb29cf64..79d09b9d70f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -715,12 +715,17 @@ 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: this function is called without holding a relation lock for database-wide + * VACUUM or ANALYZE, so the relation can be dropped concurrently. In that + * case, issue a WARNING and return false. */ 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); @@ -733,16 +738,22 @@ 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, &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 +764,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