From d7e94aba8eea2fc5d18a670cfa5e068bdcbcdf26 Mon Sep 17 00:00:00 2001
From: Manu <manuelreyesbravo@gmail.com>
Date: Tue, 29 Sep 2026 13:01:10 -0300
Subject: [PATCH v1] Index pg_constraint.confrelid to avoid a seqscan in ATTACH
 PARTITION

CloneFkReferenced() collects the foreign keys that reference a
partitioned table by looking for pg_constraint rows whose confrelid is
the table.  pg_constraint had no index on confrelid, so this was a
sequential scan of the whole catalog on every ATTACH PARTITION.

That was cheap while pg_constraint stayed small.  Since not-null
constraints gained pg_constraint rows (commit 14e87ffa5c5), the catalog
holds a row per not-null column, and the scan's cost now grows with the
total number of constraints in the database.  Attaching partitions to a
table in a large schema became noticeably slow as a result.

Add a btree index on pg_constraint.confrelid and scan through it in
CloneFkReferenced().  Only foreign keys set confrelid, so the scan keys
on confrelid alone and filters contype in the loop.  With ~1M not-null
constraints this drops the per-ATTACH time from ~25 ms to ~0.3 ms and
keeps it flat as the catalog grows.

Catalog indexes cannot be partial, so the index covers every row,
including the confrelid = 0 majority; a confrelid <> 0 partial index
would be far smaller but is not supported by the bootstrap and
CatalogIndexInsert paths.

Reported-by: Bernhard Wonisch <bernhard.wonisch@gmx.at>
Discussion: https://postgr.es/m/trinity-08d3329b-f6b2-4c11-91d7-2198e50bf4fc-1790681922354@trinity-msg-rest-gmx-gmx-live-58cc8f554d-c56lm
---
 src/backend/commands/tablecmds.c    | 17 +++++++++++------
 src/include/catalog/catversion.h    |  2 +-
 src/include/catalog/pg_constraint.h |  1 +
 3 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0274d892f2e..c246fa475d5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11355,16 +11355,21 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 	ScanKeyInit(&key[0],
 				Anum_pg_constraint_confrelid, BTEqualStrategyNumber,
 				F_OIDEQ, ObjectIdGetDatum(RelationGetRelid(parentRel)));
-	ScanKeyInit(&key[1],
-				Anum_pg_constraint_contype, BTEqualStrategyNumber,
-				F_CHAREQ, CharGetDatum(CONSTRAINT_FOREIGN));
-	/* This is a seqscan, as we don't have a usable index ... */
-	scan = systable_beginscan(pg_constraint, InvalidOid, true,
-							  NULL, 2, key);
+	/*
+	 * Look this up through the index on confrelid rather than seqscanning all
+	 * of pg_constraint.  That scan grew expensive once not-null constraints
+	 * started to have pg_constraint rows, making its cost scale with the total
+	 * number of constraints in the database.  Only foreign keys set confrelid,
+	 * so filtering on contype in the loop is just belt-and-suspenders.
+	 */
+	scan = systable_beginscan(pg_constraint, ConstraintConfRelidIndexId, true,
+							  NULL, 1, key);
 	while ((tuple = systable_getnext(scan)) != NULL)
 	{
 		Form_pg_constraint constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
 
+		if (constrForm->contype != CONSTRAINT_FOREIGN)
+			continue;
 		clone = lappend_oid(clone, constrForm->oid);
 	}
 	systable_endscan(scan);
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 6f3e526de96..d636e7a7f30 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	202609152
+#define CATALOG_VERSION_NO	202609291
 
 #endif
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index e8d27546ed9..47d57f29488 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -185,6 +185,7 @@ DECLARE_UNIQUE_INDEX(pg_constraint_conrelid_contypid_conname_index, 2665, Constr
 DECLARE_INDEX(pg_constraint_contypid_index, 2666, ConstraintTypidIndexId, pg_constraint, btree(contypid oid_ops));
 DECLARE_UNIQUE_INDEX_PKEY(pg_constraint_oid_index, 2667, ConstraintOidIndexId, pg_constraint, btree(oid oid_ops));
 DECLARE_INDEX(pg_constraint_conparentid_index, 2579, ConstraintParentIndexId, pg_constraint, btree(conparentid oid_ops));
+DECLARE_INDEX(pg_constraint_confrelid_index, 9370, ConstraintConfRelidIndexId, pg_constraint, btree(confrelid oid_ops));
 
 MAKE_SYSCACHE(CONSTROID, pg_constraint_oid_index, 16);
 
-- 
2.55.0

