Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wh93J-000295-2q for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 16:53:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wh93I-001pFr-13 for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Jul 2026 16:53:45 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wh93H-001pFg-2w for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 16:53:44 +0000 Received: from fhigh-b4-smtp.messagingengine.com ([202.12.124.155]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wh93F-00000000129-1v83 for pgsql-hackers@lists.postgresql.org; Tue, 07 Jul 2026 16:53:43 +0000 Received: from phl-compute-04.internal (phl-compute-04.internal [10.202.2.44]) by mailfhigh.stl.internal (Postfix) with ESMTP id 61C7B7A0152; Tue, 7 Jul 2026 12:53:41 -0400 (EDT) Received: from phl-frontend-04 ([10.202.2.163]) by phl-compute-04.internal (MEProxy); Tue, 07 Jul 2026 12:53:41 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kurilemu.de; h= cc:cc:content-transfer-encoding:content-type:content-type:date :date:from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to; s=fm3; t=1783443221; x= 1783529621; bh=BcgT9ROJWhPMwVnj4EkF1OGBZqo/VLxkh2USFFOjPfo=; b=S lK4S+0iyuizMz8schNs4iqdZBIclw8lrdnHeXNUeSn3PtE/mc4/KNXcRueeMeDeG cWKUfCR6K/dBcrgzfBAMCBKcoWfLToxJVezWMFeiQqr/TO14l0DKUoa6n0dWe4J1 KR2sPNZzvHjwlf+eRiXTed3xxyzE4mr1yZ/ZGX1WQAl5sDzk+KElzaOohdTue6k6 hStsw72nnjMmuNiChK72OcVm5ctl2uVPKfryjvwPPfLGcANXLlbXmaCYqImd4MrR ye8A6GYdr/IbVef0vcC7iLm0cTrOC4rPZyOm/lnLO+BfGZEmmv7Xi4UX0TJe7GE/ dxjasjfOJzK0TFvvbJYXw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm2; t=1783443221; x=1783529621; bh=B cgT9ROJWhPMwVnj4EkF1OGBZqo/VLxkh2USFFOjPfo=; b=oHldV+NWxbjFpF5zw zw6zMFGq6T0CKGHufZvtEnT694CXUiIuSjCHn2n2rIXhrpqKNlMJti8tKkLbOVZT P7LCOLIAo4t6Jek7fLWlTKou82F1+6UM4z6Ah3FwhdpJbjGZeIbZS6KP96RP1QPf Ok2UFAo1q3H/FYBDqUwD3eOTbepbxlctBKLCypRSTvBrg9jZqlAFrocjJWQPqEii CULeMYKeK9ww0p56HBi570WLGkR1aCQnyrPvESFHvU7d7pcdGhBlnoKkeO5wMbe4 cTwZFJ41B/esbIxF+nPB6Yv4AxbC9Jqn8YADhdKdTQlBnxY36ujwSPsR21nx0mQZ B53jg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: dmFkZTGmc8LkFYJgJnUlSRk1mMXrVCstp+Uq0MlZijjZElr/wtU5hK7ol2QJEMuDdoMsEI 1o+EO09FraylXACztN9NGPQGqe0sydWMOAOh/qmqPFaS5SLV38lHehMrvj4CBq/DNwMrHX b4JIrwnyrO3imAlwiV7SiWP36hBIIojuoN19uNZijcm8IXkYpbMyyBn/H4uO/Lkl4jmYE3 XbMLToY0Ge4RW3VJ4UmHvYtjq/k1Lh18Um8azG7Fqg8tbU2y24IVGQPlt1dOI++2Gt+VbO A59o+SbuTr2tsf/5rJTrxn8mFVGp5O8/veu9iDremCTm7xs6vGyJRTXGTo0wugGk3AS6Do DdVJ9F/rCWT4sP0D9uq71AN7s4JGWSI/4pVar7B/6SqWVhhe4Ui57nFlwftBAjOkcMtVpz zEiKxzVak9SNBwx+ObWMdYemuTAYiQQ1dCKkFKOUh4BGGxE07Xwre6NpJ18/XE0dPGftij uezqSTq7lb2+fIaZzbYVWuWX93RWtI/gf5hzHveuiLCduTbsZ3UzeIfnh8aLsE0/5290Um 8b0go5DgNWQXvRT7LuXzE7R3h4hyc55NEO2ejJm92jO/AqPA0DcNu1j1DBP8IZnSitD4gE sHru7ehONKRyKJt4FXaJoGzI14NFaPhb6oOdUXfWbK3OMCc2LBoJs+8mn2RQ X-ME-Proxy: Feedback-ID: ie3de48e3:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Tue, 7 Jul 2026 12:53:40 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kurilemu.de; s=schmee; t=1783443219; bh=a/pOd9L3jXZ58BMlA5vMNiA6nHgegf76o2tX84G+4+0=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=GdSGJCvIF5eT7qBDyVtlWUyxuOWglJcVeKs+Bqtxr4M5POUduolVcy1HIoFwSK960 OBmTuRrg5b+7lnYPcjulA6RmJtkOWQHMAwxM+hPwQWhWKx9cqt6mus3efFwMEktoUl B8di4Mjbml47KAoPo3mr1RO9KJMcEkmzGlg8fN7F6NOMIUZwl5OyyItZKjwEKZX2h0 CKm3lPo8EbcVRFhXFe3yvj10c/myhvztWWzcoehgtwKd0naFWYAKu461S+Q63H78rp BkAz7I8CGJr8kZcvHNXNBiwzTQ9cg4U/skfjXTLH/Q2t9dVbYO2/g+2SObmQ5UkHix R2BknsSFcrbAA== Received: by ida.kurilemu.internal (Postfix, from userid 1000) id 1CAE9B0067A; Tue, 07 Jul 2026 18:53:39 +0200 (CEST) Date: Tue, 7 Jul 2026 18:53:39 +0200 From: =?utf-8?Q?=C3=81lvaro?= Herrera To: cca5507 Cc: pgsql-hackers Subject: Re: Do not lock tables in get_tables_to_repack Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="op6mnexl7cn72cto" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --op6mnexl7cn72cto Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On 2026-Jun-16, cca5507 wrote: > Hi hackers, > > When doing a whole database repack, we build a list of repackable > tables and take a lock on them to prevent concurrent drops. But > concurrent drops can always happen after we build the list because > we process each table in a separate transaction. Not only that. We have actually three ways to obtain the list of tables to repack, and only one of these obtains the locks. So this code is internally inconsistent. I agree that we should do something like your patch. I wanted to be a little more defensive though; how about the attached? -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ --op6mnexl7cn72cto Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename=v2-0001-Do-not-lock-tables-in-get_tables_to_repack.patch From 5ae359740406ae10db552811424f38aa4faeabdc Mon Sep 17 00:00:00 2001 From: ChangAo Chen Date: Tue, 16 Jun 2026 14:49:49 +0800 Subject: [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). When doing a whole database repack, we build a list of repackable tables and take a lock on them to prevent concurrent drops. But concurrent drops can always happen after we build the list because we process each table in a separate transaction. The ConditionalLockRelationOid() also makes the default behavior like SKIP_LOCKED, which is unexpected. To remove the locks, we need to make repack_is_permitted_for_relation() handles concurrent drops correctly: it should not report an error when failing to search the syscache in pg_class_aclcheck(). Use pg_class_aclcheck_ext() instead to detect a concurrent drop. Also check the return value of get_rel_name(). While at it, replace relation_close() with table_close() to match the table_open(). --- src/backend/commands/repack.c | 67 ++++++++++------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index faa07d1a118..2879c8af574 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2169,22 +2169,9 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - /* - * Try to obtain a light lock on the index's table, to ensure it - * doesn't go away while we collect the list. If we cannot, just - * disregard it. Be sure to release this if we ultimately decide - * not to process the table! - */ - if (!ConditionalLockRelationOid(index->indrelid, AccessShareLock)) - continue; - - /* Verify that the table still exists; skip if not */ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) - { - UnlockRelationOid(index->indrelid, AccessShareLock); continue; - } classForm = (Form_pg_class) GETSTRUCT(classtup); /* Skip temp relations belonging to other sessions */ @@ -2192,7 +2179,6 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) !isTempOrTempToastNamespace(classForm->relnamespace)) { ReleaseSysCache(classtup); - UnlockRelationOid(index->indrelid, AccessShareLock); continue; } @@ -2201,10 +2187,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, GetUserId())) - { - UnlockRelationOid(index->indrelid, AccessShareLock); continue; - } /* Use a permanent memory context for the result list */ oldcxt = MemoryContextSwitchTo(permcxt); @@ -2228,45 +2211,20 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) class = (Form_pg_class) GETSTRUCT(tuple); - /* - * Try to obtain a light lock on the table, to ensure it doesn't - * go away while we collect the list. If we cannot, just - * disregard the table. Be sure to release this if we ultimately - * decide not to process the table! - */ - if (!ConditionalLockRelationOid(class->oid, AccessShareLock)) - continue; - - /* Verify that the table still exists */ - if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(class->oid))) - { - UnlockRelationOid(class->oid, AccessShareLock); - continue; - } - /* Can only process plain tables and matviews */ if (class->relkind != RELKIND_RELATION && class->relkind != RELKIND_MATVIEW) - { - UnlockRelationOid(class->oid, AccessShareLock); continue; - } /* Skip temp relations belonging to other sessions */ if (class->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(class->relnamespace)) - { - UnlockRelationOid(class->oid, AccessShareLock); continue; - } /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, class->oid, GetUserId())) - { - UnlockRelationOid(class->oid, AccessShareLock); continue; - } /* Use a permanent memory context for the result list */ oldcxt = MemoryContextSwitchTo(permcxt); @@ -2279,7 +2237,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) } table_endscan(scan); - relation_close(catalog, AccessShareLock); + table_close(catalog, AccessShareLock); return rtcs; } @@ -2357,15 +2315,28 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { + bool is_missing = false; + Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK) + if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) return true; - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), - get_rel_name(relid))); + /* Report a warning if the relation still exists. */ + if (!is_missing) + { + char *relname; + + relname = get_rel_name(relid); + if (relname != NULL) + { + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + + pfree(relname); + } + } return false; } -- 2.47.3 --op6mnexl7cn72cto Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename=v2-0002-fixups.patch From 12395a8ba804799dedc0e58ceec60bc0822a3a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Tue, 7 Jul 2026 18:35:28 +0200 Subject: [PATCH v2 2/2] fixups --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto--