agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v6] Avoid orphaned objects dependencies 186+ messages / 5 participants [nested] [flat]
* [PATCH v6] Avoid orphaned objects dependencies @ 2024-03-29 15:43 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: Bertrand Drouvot @ 2024-03-29 15:43 UTC (permalink / raw) It's currently possible to create orphaned objects dependencies, for example: Scenario 1: session 1: begin; drop schema schem; session 2: create a function in the schema schem session 1: commit; With the above, the function created in session 2 would be linked to a non existing schema. Scenario 2: session 1: begin; create a function in the schema schem session 2: drop schema schem; session 1: commit; With the above, the function created in session 1 would be linked to a non existing schema. To avoid those scenarios, a new lock (that conflicts with a lock taken by DROP) has been put in place when the dependencies are being recorded. With this in place, the drop schema in scenario 2 would be locked. Also, after the new lock attempt, the patch checks that the object still exists: with this in place session 2 in scenario 1 would be locked and would report an error once session 1 committs (that would not be the case should session 1 abort the transaction). If the object is dropped before the new lock attempt is triggered then the patch would also report an error (but with less details). The patch adds a few tests for some dependency cases (that would currently produce orphaned objects): - schema and function (as the above scenarios) - alter a dependency (function and schema) - function and arg type - function and return type - function and function - domain and domain - table and type - server and foreign data wrapper --- src/backend/catalog/dependency.c | 54 ++++++++ src/backend/catalog/objectaddress.c | 70 ++++++++++ src/backend/catalog/pg_depend.c | 12 ++ src/include/catalog/dependency.h | 1 + src/include/catalog/objectaddress.h | 1 + .../expected/test_dependencies_locks.out | 129 ++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + .../specs/test_dependencies_locks.spec | 89 ++++++++++++ 8 files changed, 357 insertions(+) 24.1% src/backend/catalog/ 45.9% src/test/isolation/expected/ 28.6% src/test/isolation/specs/ diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index d4b5b2ade1..a49357bbe2 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1517,6 +1517,60 @@ AcquireDeletionLock(const ObjectAddress *object, int flags) } } +/* + * depLockAndCheckObject + * + * Lock the object that we are about to record a dependency on. + * After it's locked, verify that it hasn't been dropped while we + * weren't looking. If the object has been dropped, this function + * does not return! + */ +void +depLockAndCheckObject(const ObjectAddress *object) +{ + char *object_description; + + /* + * Those don't rely on LockDatabaseObject() when being dropped (see + * AcquireDeletionLock()). Also it looks like they can not produce + * orphaned dependent objects when being dropped. + */ + if (object->classId == RelationRelationId || object->classId == AuthMemRelationId) + return; + + object_description = getObjectDescription(object, true); + + /* assume we should lock the whole object not a sub-object */ + LockDatabaseObject(object->classId, object->objectId, 0, AccessShareLock); + + /* check if object still exists */ + if (!ObjectByIdExist(object, false)) + { + /* + * It might be possible that we are creating it (for example creating + * a composite type while creating a relation), so bypass the syscache + * lookup and use a dirty snaphot instead to cover this scenario. + */ + if (!ObjectByIdExist(object, true)) + { + /* + * If the object has been dropped before we get a chance to get + * its description, then emit a generic error message. That looks + * like a good compromise over extra complexity. + */ + if (object_description) + ereport(ERROR, errmsg("%s does not exist", object_description)); + else + ereport(ERROR, errmsg("a dependent object does not exist")); + } + } + + if (object_description) + pfree(object_description); + + return; +} + /* * ReleaseDeletionLock - release an object deletion lock * diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 7b536ac6fd..c2b873dd81 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -2590,6 +2590,76 @@ get_object_namespace(const ObjectAddress *address) return oid; } +/* + * ObjectByIdExist + * + * Return whether the given object exists. + * + * Works for most catalogs, if no special processing is needed. + */ +bool +ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot) +{ + HeapTuple tuple; + int cache = -1; + const ObjectPropertyType *property; + + if (!use_dirty_snapshot) + { + property = get_object_property_data(address->classId); + + cache = property->oid_catcache_id; + } + + if (cache >= 0) + { + /* Fetch tuple from syscache. */ + tuple = SearchSysCache1(cache, ObjectIdGetDatum(address->objectId)); + + if (!HeapTupleIsValid(tuple)) + { + return false; + } + + ReleaseSysCache(tuple); + + return true; + } + else + { + Relation rel; + ScanKeyData skey[1]; + SysScanDesc scan; + SnapshotData DirtySnapshot; + Snapshot snapshot; + + if (use_dirty_snapshot) + { + InitDirtySnapshot(DirtySnapshot); + snapshot = &DirtySnapshot; + } + else + snapshot = NULL; + + rel = table_open(address->classId, AccessShareLock); + + ScanKeyInit(&skey[0], + get_object_attnum_oid(address->classId), + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(address->objectId)); + + scan = systable_beginscan(rel, get_object_oid_index(address->classId), true, + snapshot, 1, skey); + + /* we expect exactly one match */ + tuple = systable_getnext(scan); + systable_endscan(scan); + table_close(rel, AccessShareLock); + + return (HeapTupleIsValid(tuple)); + } +} + /* * Return ObjectType for the given object type as given by * getObjectTypeDescription; if no valid ObjectType code exists, but it's a diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c index 5366f7820c..f16af28429 100644 --- a/src/backend/catalog/pg_depend.c +++ b/src/backend/catalog/pg_depend.c @@ -108,6 +108,12 @@ recordMultipleDependencies(const ObjectAddress *depender, if (isObjectPinned(referenced)) continue; + /* + * Acquire a lock and check object still exists while recording the + * dependency. XXX - Should we do so only for DEPENDENCY_NORMAL? + */ + depLockAndCheckObject(referenced); + if (slot_init_count < max_slots) { slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc), @@ -506,6 +512,12 @@ changeDependencyFor(Oid classId, Oid objectId, return 1; } + /* + * Acquire a lock and check object still exists while changing the + * dependency. + */ + depLockAndCheckObject(&objAddr); + depRel = table_open(DependRelationId, RowExclusiveLock); /* There should be existing dependency record(s), so search. */ diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 7eee66f810..5619b6f55e 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -101,6 +101,7 @@ typedef struct ObjectAddresses ObjectAddresses; /* in dependency.c */ extern void AcquireDeletionLock(const ObjectAddress *object, int flags); +extern void depLockAndCheckObject(const ObjectAddress *object); extern void ReleaseDeletionLock(const ObjectAddress *object); diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h index 3a70d80e32..04891abcc1 100644 --- a/src/include/catalog/objectaddress.h +++ b/src/include/catalog/objectaddress.h @@ -53,6 +53,7 @@ extern void check_object_ownership(Oid roleid, Node *object, Relation relation); extern Oid get_object_namespace(const ObjectAddress *address); +extern bool ObjectByIdExist(const ObjectAddress *address, bool use_dirty_snapshot); extern bool is_objectclass_supported(Oid class_id); extern const char *get_object_class_descr(Oid class_id); diff --git a/src/test/isolation/expected/test_dependencies_locks.out b/src/test/isolation/expected/test_dependencies_locks.out new file mode 100644 index 0000000000..9b645d7aa5 --- /dev/null +++ b/src/test/isolation/expected/test_dependencies_locks.out @@ -0,0 +1,129 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit +step s1_begin: BEGIN; +step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; +step s2_drop_schema: DROP SCHEMA testschema; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_schema: <... completed> +ERROR: cannot drop schema testschema because other objects depend on it + +starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit +step s2_begin: BEGIN; +step s2_drop_schema: DROP SCHEMA testschema; +step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...> +step s2_commit: COMMIT; +step s1_create_function_in_schema: <... completed> +ERROR: schema testschema does not exist + +starting permutation: s1_begin s1_alter_function_schema s2_drop_alterschema s1_commit +step s1_begin: BEGIN; +step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema; +step s2_drop_alterschema: DROP SCHEMA alterschema; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_alterschema: <... completed> +ERROR: cannot drop schema alterschema because other objects depend on it + +starting permutation: s2_begin s2_drop_alterschema s1_alter_function_schema s2_commit +step s2_begin: BEGIN; +step s2_drop_alterschema: DROP SCHEMA alterschema; +step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema; <waiting ...> +step s2_commit: COMMIT; +step s1_alter_function_schema: <... completed> +ERROR: schema alterschema does not exist + +starting permutation: s1_begin s1_create_function_with_argtype s2_drop_foo_type s1_commit +step s1_begin: BEGIN; +step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; +step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_foo_type: <... completed> +ERROR: cannot drop type foo because other objects depend on it + +starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_argtype s2_commit +step s2_begin: BEGIN; +step s2_drop_foo_type: DROP TYPE public.foo; +step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...> +step s2_commit: COMMIT; +step s1_create_function_with_argtype: <... completed> +ERROR: type foo does not exist + +starting permutation: s1_begin s1_create_function_with_rettype s2_drop_foo_rettype s1_commit +step s1_begin: BEGIN; +step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; +step s2_drop_foo_rettype: DROP DOMAIN id; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_foo_rettype: <... completed> +ERROR: cannot drop type id because other objects depend on it + +starting permutation: s2_begin s2_drop_foo_rettype s1_create_function_with_rettype s2_commit +step s2_begin: BEGIN; +step s2_drop_foo_rettype: DROP DOMAIN id; +step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; <waiting ...> +step s2_commit: COMMIT; +step s1_create_function_with_rettype: <... completed> +ERROR: type id does not exist + +starting permutation: s1_begin s1_create_function_with_function s2_drop_function_f s1_commit +step s1_begin: BEGIN; +step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; +step s2_drop_function_f: DROP FUNCTION f(); <waiting ...> +step s1_commit: COMMIT; +step s2_drop_function_f: <... completed> +ERROR: cannot drop function f() because other objects depend on it + +starting permutation: s2_begin s2_drop_function_f s1_create_function_with_function s2_commit +step s2_begin: BEGIN; +step s2_drop_function_f: DROP FUNCTION f(); +step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; <waiting ...> +step s2_commit: COMMIT; +step s1_create_function_with_function: <... completed> +ERROR: function f() does not exist + +starting permutation: s1_begin s1_create_domain_with_domain s2_drop_domain_id s1_commit +step s1_begin: BEGIN; +step s1_create_domain_with_domain: CREATE DOMAIN idid as id; +step s2_drop_domain_id: DROP DOMAIN id; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_domain_id: <... completed> +ERROR: cannot drop type id because other objects depend on it + +starting permutation: s2_begin s2_drop_domain_id s1_create_domain_with_domain s2_commit +step s2_begin: BEGIN; +step s2_drop_domain_id: DROP DOMAIN id; +step s1_create_domain_with_domain: CREATE DOMAIN idid as id; <waiting ...> +step s2_commit: COMMIT; +step s1_create_domain_with_domain: <... completed> +ERROR: type id does not exist + +starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit +step s1_begin: BEGIN; +step s1_create_table_with_type: CREATE TABLE tabtype(a footab); +step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_footab_type: <... completed> +ERROR: cannot drop type footab because other objects depend on it + +starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit +step s2_begin: BEGIN; +step s2_drop_footab_type: DROP TYPE public.footab; +step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...> +step s2_commit: COMMIT; +step s1_create_table_with_type: <... completed> +ERROR: type footab does not exist + +starting permutation: s1_begin s1_create_server_with_fdw_wrapper s2_drop_fdw_wrapper s1_commit +step s1_begin: BEGIN; +step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; +step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; <waiting ...> +step s1_commit: COMMIT; +step s2_drop_fdw_wrapper: <... completed> +ERROR: cannot drop foreign-data wrapper fdw_wrapper because other objects depend on it + +starting permutation: s2_begin s2_drop_fdw_wrapper s1_create_server_with_fdw_wrapper s2_commit +step s2_begin: BEGIN; +step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; +step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; <waiting ...> +step s2_commit: COMMIT; +step s1_create_server_with_fdw_wrapper: <... completed> +ERROR: foreign-data wrapper fdw_wrapper does not exist diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index 0342eb39e4..1b67f0bffe 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -114,3 +114,4 @@ test: serializable-parallel-2 test: serializable-parallel-3 test: matview-write-skew test: lock-nowait +test: test_dependencies_locks diff --git a/src/test/isolation/specs/test_dependencies_locks.spec b/src/test/isolation/specs/test_dependencies_locks.spec new file mode 100644 index 0000000000..5d04dfe9dc --- /dev/null +++ b/src/test/isolation/specs/test_dependencies_locks.spec @@ -0,0 +1,89 @@ +setup +{ + CREATE SCHEMA testschema; + CREATE SCHEMA alterschema; + CREATE TYPE public.foo as enum ('one', 'two'); + CREATE TYPE public.footab as enum ('three', 'four'); + CREATE DOMAIN id AS int; + CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1; + CREATE FUNCTION public.falter() RETURNS int LANGUAGE SQL RETURN 1; + CREATE FOREIGN DATA WRAPPER fdw_wrapper; +} + +teardown +{ + DROP FUNCTION IF EXISTS testschema.foo(); + DROP FUNCTION IF EXISTS fooargtype(num foo); + DROP FUNCTION IF EXISTS footrettype(); + DROP FUNCTION IF EXISTS foofunc(); + DROP FUNCTION IF EXISTS public.falter(); + DROP FUNCTION IF EXISTS alterschema.falter(); + DROP DOMAIN IF EXISTS idid; + DROP SERVER IF EXISTS srv_fdw_wrapper; + DROP TABLE IF EXISTS tabtype; + DROP SCHEMA IF EXISTS testschema; + DROP SCHEMA IF EXISTS alterschema; + DROP TYPE IF EXISTS public.foo; + DROP TYPE IF EXISTS public.footab; + DROP DOMAIN IF EXISTS id; + DROP FUNCTION IF EXISTS f(); + DROP FOREIGN DATA WRAPPER IF EXISTS fdw_wrapper; +} + +session "s1" + +step "s1_begin" { BEGIN; } +step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; } +step "s1_create_function_with_argtype" { CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; } +step "s1_create_function_with_rettype" { CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; } +step "s1_create_function_with_function" { CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; } +step "s1_alter_function_schema" { ALTER FUNCTION public.falter() SET SCHEMA alterschema; } +step "s1_create_domain_with_domain" { CREATE DOMAIN idid as id; } +step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); } +step "s1_create_server_with_fdw_wrapper" { CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; } +step "s1_commit" { COMMIT; } + +session "s2" + +step "s2_begin" { BEGIN; } +step "s2_drop_schema" { DROP SCHEMA testschema; } +step "s2_drop_alterschema" { DROP SCHEMA alterschema; } +step "s2_drop_foo_type" { DROP TYPE public.foo; } +step "s2_drop_foo_rettype" { DROP DOMAIN id; } +step "s2_drop_footab_type" { DROP TYPE public.footab; } +step "s2_drop_function_f" { DROP FUNCTION f(); } +step "s2_drop_domain_id" { DROP DOMAIN id; } +step "s2_drop_fdw_wrapper" { DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; } +step "s2_commit" { COMMIT; } + +# function - schema +permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit" +permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit" + +# alter function - schema +permutation "s1_begin" "s1_alter_function_schema" "s2_drop_alterschema" "s1_commit" +permutation "s2_begin" "s2_drop_alterschema" "s1_alter_function_schema" "s2_commit" + +# function - argtype +permutation "s1_begin" "s1_create_function_with_argtype" "s2_drop_foo_type" "s1_commit" +permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_argtype" "s2_commit" + +# function - rettype +permutation "s1_begin" "s1_create_function_with_rettype" "s2_drop_foo_rettype" "s1_commit" +permutation "s2_begin" "s2_drop_foo_rettype" "s1_create_function_with_rettype" "s2_commit" + +# function - function +permutation "s1_begin" "s1_create_function_with_function" "s2_drop_function_f" "s1_commit" +permutation "s2_begin" "s2_drop_function_f" "s1_create_function_with_function" "s2_commit" + +# domain - domain +permutation "s1_begin" "s1_create_domain_with_domain" "s2_drop_domain_id" "s1_commit" +permutation "s2_begin" "s2_drop_domain_id" "s1_create_domain_with_domain" "s2_commit" + +# table - type +permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit" +permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit" + +# server - foreign data wrapper +permutation "s1_begin" "s1_create_server_with_fdw_wrapper" "s2_drop_fdw_wrapper" "s1_commit" +permutation "s2_begin" "s2_drop_fdw_wrapper" "s1_create_server_with_fdw_wrapper" "s2_commit" -- 2.34.1 --rJmn4JZRBVtiOSY4-- ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). @ 2026-06-16 06:49 ChangAo Chen <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: ChangAo Chen @ 2026-06-16 06:49 UTC (permalink / raw) 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 ^ permalink raw reply [nested|flat] 186+ messages in thread
* Do not lock tables in get_tables_to_repack @ 2026-06-16 07:34 =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 0 siblings, 2 replies; 186+ messages in thread From: =?utf-8?B?Y2NhNTUwNw==?= @ 2026-06-16 07:34 UTC (permalink / raw) To: =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]> 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. 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(). Thoughts? -- Regards, ChangAo Chen Attachments: [application/octet-stream] v1-0001-Do-not-lock-tables-in-get_tables_to_repack.patch (5.4K, ../../[email protected]/2-v1-0001-Do-not-lock-tables-in-get_tables_to_repack.patch) download | inline diff: From fa5c3073f2f41cf3c8ab7c9663b681e6140a4824 Mon Sep 17 00:00:00 2001 From: ChangAo Chen <[email protected]> Date: Tue, 16 Jun 2026 14:49:49 +0800 Subject: [PATCH v1] 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 ec100e3eef5..6f25effeed2 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2159,22 +2159,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 */ @@ -2182,7 +2169,6 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) !isTempOrTempToastNamespace(classForm->relnamespace)) { ReleaseSysCache(classtup); - UnlockRelationOid(index->indrelid, AccessShareLock); continue; } @@ -2191,10 +2177,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); @@ -2218,45 +2201,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); @@ -2269,7 +2227,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) } table_endscan(scan); - relation_close(catalog, AccessShareLock); + table_close(catalog, AccessShareLock); return rtcs; } @@ -2347,15 +2305,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.34.1 ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-06-27 19:56 Bharath Rupireddy <[email protected]> parent: =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 1 sibling, 0 replies; 186+ messages in thread From: Bharath Rupireddy @ 2026-06-27 19:56 UTC (permalink / raw) To: cca5507 <[email protected]>; +Cc: pgsql-hackers <[email protected]>; Antonin Houska <[email protected]>; Alvaro Herrera <[email protected]> Hi, On Tue, Jun 16, 2026 at 12:35 AM cca5507 <[email protected]> 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. 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(). > > Thoughts? I don't have a strong opinion on fixing this for REPACK. For vacuum, we wanted to get consistent behavior across all modes of invoking it [1]. Whether to handle concurrent drops for REPACK depends on how long it spends scanning pg_index/pg_class in get_tables_to_repack. Also, vacuum is more commonly run database-wide (against all tables), and without field experience showing this is a real problem for REPACK, I'm not sure the fix is needed. That said, others may have a different take - adding the REPACK authors here for their input. [1] https://www.postgresql.org/message-id/CALj2ACUbhDFJWSwiyoUxXED2S2fr9Xis%3D08Rnjq53UzBE4FvzA%40mail.g... -- Bharath Rupireddy Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-07 16:53 Álvaro Herrera <[email protected]> parent: =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 1 sibling, 1 reply; 186+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:53 UTC (permalink / raw) To: cca5507 <[email protected]>; +Cc: pgsql-hackers <[email protected]> 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/ ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-08 02:36 =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> parent: Álvaro Herrera <[email protected]> 0 siblings, 1 reply; 186+ messages in thread From: =?utf-8?B?Y2NhNTUwNw==?= @ 2026-07-08 02:36 UTC (permalink / raw) To: =?utf-8?B?w4FsdmFybyBIZXJyZXJh?= <[email protected]>; +Cc: =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]> > > 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? - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); Do we really need to copy it? We hold a refcount on it so it won't be freed until we release it. Otherwise LGTM. -- Regards, ChangAo Chen ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-10 14:22 Álvaro Herrera <[email protected]> parent: =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 0 siblings, 1 reply; 186+ messages in thread From: Álvaro Herrera @ 2026-07-10 14:22 UTC (permalink / raw) To: cca5507 <[email protected]>; +Cc: pgsql-hackers <[email protected]> On 2026-Jul-08, cca5507 wrote: > - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); > + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); > > Do we really need to copy it? We hold a refcount on it so it won't be freed > until we release it. Otherwise LGTM. Yeah, I guess it doesn't matter. I have removed the copy and updated some comments. I also realized that there are some places where we weren't dealing correctly with the possibility that the relation goes away, or is replaced with something different, so I added that too. While looking at it I also realized that get_tables_to_repack_partitioned is likewise not careful enough about it: we do IndexGetRelation(, false) which fails hard if the pg_index tuple cannot be found, which is the wrong thing to do. At the same time, it's annoying that half of the code that clearly belongs in that routine is actually in ExecRepack(). I moved that to where it rightfully belongs. (The only somewhat annoying thing is that we have to NULL-out the Relation pointer after returning; but that's not *too* bad IMO.) Any opinions on this? -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "No me acuerdo, pero no es cierto. No es cierto, y si fuera cierto, no me acuerdo." (Augusto Pinochet a una corte de justicia) ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-10 16:03 Bharath Rupireddy <[email protected]> parent: Álvaro Herrera <[email protected]> 0 siblings, 1 reply; 186+ messages in thread From: Bharath Rupireddy @ 2026-07-10 16:03 UTC (permalink / raw) To: Álvaro Herrera <[email protected]>; +Cc: cca5507 <[email protected]>; pgsql-hackers <[email protected]> Hi, On Fri, Jul 10, 2026 at 7:22 AM Álvaro Herrera <[email protected]> wrote: > > On 2026-Jul-08, cca5507 wrote: > > > - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); > > + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); > > > > Do we really need to copy it? We hold a refcount on it so it won't be freed > > until we release it. Otherwise LGTM. Thanks for committing this. Here is my overall take. Even before this commit, a database-wide REPACK does not error on a concurrently dropped table. get_tables_to_repack takes a brief lock on each table before the ACL check, which blocks a concurrent drop while the list is being built. Later each table is re-opened under its lock and skipped if it is already gone. So the brief lock is not needed for correctness. What it does is let an unprivileged user lock relations it may not even be allowed to repack, including system catalogs, before we have checked its privileges. I am fine with removing the lock as the commit did. I would also like to tighten repack_is_permitted_for_relation so a caller that holds a lock still errors out on a missing relation instead of skipping it silently (https://www.postgresql.org/message-id/akPhEffRipH4isWF%40nathan). Attached as a follow-up patch. Please have a look. > Yeah, I guess it doesn't matter. I have removed the copy and updated > some comments. I also realized that there are some places where we > weren't dealing correctly with the possibility that the relation goes > away, or is replaced with something different, so I added that too. I agree that the SearchSysCacheCopy1 in v2-0002 adds no benefit. Since no lock is held on the relation, there is no point in copying the syscache tuple. > While looking at it I also realized that get_tables_to_repack_partitioned > is likewise not careful enough about it: we do IndexGetRelation(, false) > which fails hard if the pg_index tuple cannot be found, which is the > wrong thing to do. > > At the same time, it's annoying that half of the code that clearly > belongs in that routine is actually in ExecRepack(). I moved that to > where it rightfully belongs. (The only somewhat annoying thing is that > we have to NULL-out the Relation pointer after returning; but that's not > *too* bad IMO.) > > Any opinions on this? I will take a closer look at this in a bit. -- Bharath Rupireddy Amazon Web Services: https://aws.amazon.com Attachments: [application/octet-stream] v1-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch (5.1K, ../../CALj2ACX3pyuRS8++6L20cJUMRTf_qbbVp69J1btJ3y6=77e5gw@mail.gmail.com/2-v1-0001-Tighten-ACL-check-in-repack_is_permitted_for_rela.patch) download | inline diff: From 6729b2f14ed623a8b38f5a530b8c85c96bfd5b40 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy <[email protected]> Date: Fri, 10 Jul 2026 15:42:47 +0000 Subject: [PATCH v1] Tighten ACL check in repack_is_permitted_for_relation() Commit c71d43025d7 made repack_is_permitted_for_relation() use _ext() and skip a concurrently dropped relation silently. That is fine for the callers that hold no lock on the relation, but it is applied unconditionally, including where we may already hold a lock on the relation whose ACL is checked. In that case missing a relation is not fine, so this made the code more brittle: a missing relation there would indicate a bug and should error out rather than be skipped silently. Add a missing_ok flag so the silent skip is done only where no lock is held. Callers that hold a lock pass false and still error out on a missing relation. --- src/backend/commands/repack.c | 38 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 02883fe34a4..b09be65da3b 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -172,8 +172,8 @@ static List *get_tables_to_repack(RepackCommand cmd, bool usingindex, static List *get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, bool rel_is_index, MemoryContext permcxt); -static bool repack_is_permitted_for_relation(RepackCommand cmd, - Oid relid, Oid userid); +static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, + Oid userid, bool missing_ok); static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt); static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot, @@ -724,7 +724,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, Assert(CheckRelationLockedByMe(OldHeap, lmode, false)); /* Check that the user still has privileges for the relation */ - if (!repack_is_permitted_for_relation(cmd, tableOid, userid)) + if (!repack_is_permitted_for_relation(cmd, tableOid, userid, false)) { relation_close(OldHeap, lmode); return false; @@ -2198,7 +2198,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())) + GetUserId(), true)) continue; /* Use a permanent memory context for the result list */ @@ -2235,7 +2235,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, class->oid, - GetUserId())) + GetUserId(), true)) continue; /* Use a permanent memory context for the result list */ @@ -2304,7 +2304,7 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, * leaf partition despite having them on the partitioned table. Skip * if so. */ - if (!repack_is_permitted_for_relation(cmd, table_oid, GetUserId())) + if (!repack_is_permitted_for_relation(cmd, table_oid, GetUserId(), true)) continue; /* Use a permanent memory context for the result list */ @@ -2321,16 +2321,21 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to execute REPACK on relid. + * Check whether the passed-in user has privileges to execute REPACK on the + * relation. * - * Caller may not have a lock on the relation, so it could have been - * dropped concurrently. In that case, silently return false. + * missing_ok should be passed only by callers that hold no lock on the + * relation, where a concurrent drop is possible and fine to skip. A caller + * that holds a lock must not pass it: there a missing relation would indicate + * a bug and should error out rather than be silently skipped. * * If the relation does exist but the user doesn't have the required - * privs, emit a WARNING and return false. Otherwise, return true. + * privileges, emit a WARNING and return false to let the caller + * decide what to do with this relation. */ static bool -repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) +repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid, + bool missing_ok) { bool is_missing = false; AclResult result; @@ -2338,9 +2343,18 @@ repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, + missing_ok ? &is_missing : NULL); + + /* + * 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; + } if (result == ACLCHECK_OK) return true; -- 2.47.3 ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-10 17:34 Bharath Rupireddy <[email protected]> parent: Bharath Rupireddy <[email protected]> 0 siblings, 1 reply; 186+ messages in thread From: Bharath Rupireddy @ 2026-07-10 17:34 UTC (permalink / raw) To: Álvaro Herrera <[email protected]>; +Cc: cca5507 <[email protected]>; pgsql-hackers <[email protected]> Hi, On Fri, Jul 10, 2026 at 9:03 AM Bharath Rupireddy <[email protected]> wrote: > > > While looking at it I also realized that get_tables_to_repack_partitioned > > is likewise not careful enough about it: we do IndexGetRelation(, false) > > which fails hard if the pg_index tuple cannot be found, which is the > > wrong thing to do. Agreed. For a single-level partitioned table, dropping a leaf blocks on the parent's AccessExclusiveLock, which REPACK holds, so the leaf can't go away mid-build. But for a multi-level table, REPACK holds a lock only on the top parent, so a lower-level leaf whose immediate parent is an intermediate table isn't protected and can be dropped concurrently. So IndexGetRelation(, false) failing hard there is indeed the wrong thing. > > At the same time, it's annoying that half of the code that clearly > > belongs in that routine is actually in ExecRepack(). I moved that to > > where it rightfully belongs. +1, that's a good cleanup. That code clearly belongs in get_tables_to_repack_partitioned(). >> (The only somewhat annoying thing is that > > we have to NULL-out the Relation pointer after returning; but that's not > > *too* bad IMO.) That's fine. We already NULL out rel after get_tables_to_repack_partitioned() today, so the patch isn't adding anything new there. Relying on a non-NULL return from process_single_relation() to mean partitioned table is a little indirect on first read, but the comments explains it well, and I wouldn't complicate the code to avoid it. > > Any opinions on this? > > I will take a closer look at this in a bit. Patch looks good to me. One nit: + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; How about a short comment on why this is needed even with a lock on the parent, since that lock doesn't cover lower-level leaves in a multi-level partition tree? -- Bharath Rupireddy Amazon Web Services: https://aws.amazon.com ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-11 14:50 =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> parent: Bharath Rupireddy <[email protected]> 0 siblings, 1 reply; 186+ messages in thread From: =?utf-8?B?Y2NhNTUwNw==?= @ 2026-07-11 14:50 UTC (permalink / raw) To: =?utf-8?B?QmhhcmF0aCBSdXBpcmVkZHk=?= <[email protected]>; =?utf-8?B?w4FsdmFybyBIZXJyZXJh?= <[email protected]>; +Cc: =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]> > > > Any opinions on this? > > > > I will take a closer look at this in a bit. > > Patch looks good to me. One nit: > > + table_oid = IndexGetRelation(child_oid, true); > + if (!OidIsValid(table_oid)) > + continue; > > How about a short comment on why this is needed even with a lock on > the parent, since that lock doesn't cover lower-level leaves in a > multi-level partition tree? I think it's worth a comment, too. Patch LGTM. -- Regards, ChangAo Chen ^ permalink raw reply [nested|flat] 186+ messages in thread
* Re: Do not lock tables in get_tables_to_repack @ 2026-07-20 10:19 Álvaro Herrera <[email protected]> parent: =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 0 siblings, 0 replies; 186+ messages in thread From: Álvaro Herrera @ 2026-07-20 10:19 UTC (permalink / raw) To: cca5507 <[email protected]>; +Cc: Bharath Rupireddy <[email protected]>; pgsql-hackers <[email protected]> On 2026-Jul-11, cca5507 wrote: > Bharath Rupireddy wrote: > > + table_oid = IndexGetRelation(child_oid, true); > > + if (!OidIsValid(table_oid)) > > + continue; > > > > How about a short comment on why this is needed even with a lock on > > the parent, since that lock doesn't cover lower-level leaves in a > > multi-level partition tree? > > I think it's worth a comment, too. Patch LGTM. Thanks, added that and pushed. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Puedes vivir sólo una vez, pero si lo haces bien, una vez es suficiente" ^ permalink raw reply [nested|flat] 186+ messages in thread
end of thread, other threads:[~2026-07-20 10:19 UTC | newest] Thread overview: 186+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2024-03-29 15:43 [PATCH v6] Avoid orphaned objects dependencies Bertrand Drouvot <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 06:49 [PATCH v2 1/2] Do not lock tables in get_tables_to_repack(). ChangAo Chen <[email protected]> 2026-06-16 07:34 Do not lock tables in get_tables_to_repack =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 2026-06-27 19:56 ` Re: Do not lock tables in get_tables_to_repack Bharath Rupireddy <[email protected]> 2026-07-07 16:53 ` Re: Do not lock tables in get_tables_to_repack Álvaro Herrera <[email protected]> 2026-07-08 02:36 ` Re: Do not lock tables in get_tables_to_repack =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 2026-07-10 14:22 ` Re: Do not lock tables in get_tables_to_repack Álvaro Herrera <[email protected]> 2026-07-10 16:03 ` Re: Do not lock tables in get_tables_to_repack Bharath Rupireddy <[email protected]> 2026-07-10 17:34 ` Re: Do not lock tables in get_tables_to_repack Bharath Rupireddy <[email protected]> 2026-07-11 14:50 ` Re: Do not lock tables in get_tables_to_repack =?utf-8?B?Y2NhNTUwNw==?= <[email protected]> 2026-07-20 10:19 ` Re: Do not lock tables in get_tables_to_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