| From: | Manu <manuelreyesbravo(at)gmail(dot)com> |
|---|---|
| To: | Álvaro Herrera <alvherre(at)kurilemu(dot)de> |
| Cc: | Bernhard Wonisch <bernhard(dot)wonisch(at)gmx(dot)at>, pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: ATTACH PARTITION cost grows linearly with pg_constraint size (seqscan in CloneFkReferenced), much worse since not-null constraints are in pg_constraint (PG 18) |
| Date: | 2026-09-29 16:08:24 |
| Message-ID: | 179069810491.316007.4145107668471017354@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
> I vaguely recall looking into adding such an index, and finding out
> that we don't support partial indexes on catalogs. Is that doable in
> some clean way nowadays? (Or maybe the index worked fine, and what
> failed was adding a syscache on top of it? Not sure.)
Thanks for the pointer -- it sent me to check, and your recollection
holds up. It's the same "no usable index" seqscan from back then; it
only stayed cheap while pg_constraint was small, and not-null
constraints moving into it for 18 is what surfaced it.
On whether a partial index is doable cleanly: still not, and there are
two separate walls, not one.
Declaration: the bootstrap grammar has no place for a predicate.
Boot_DeclareIndexStmt in src/backend/bootstrap/bootparse.y is just
"DECLARE INDEX name oid ON table USING am ( params )", no WHERE. genbki
does pass the predicate string through into postgres.bki, so the build
succeeds, but initdb then fails with a syntax error at that line.
Maintenance: even past that, the catalog insert path assumes
non-partial. CatalogIndexInsert() in src/backend/catalog/indexing.c:
/*
* Expressional and partial indexes on system catalogs are not
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Predicate == NIL);
It never evaluates a predicate. Forcing a partial index in with
allow_system_table_mods confirms it: after ~1M not-null rows the
"WHERE confrelid <> 0" index holds all of them rather than the one FK
row, silently on a non-assert build. So a clean partial catalog index
would mean teaching both the bootstrap grammar and CatalogIndexInsert to
carry and evaluate a predicate.
The syscache isn't the blocker here. CloneFkReferenced() scans with
systable_beginscan(pg_constraint, InvalidOid, true, ...), not a syscache
lookup, so a plain (non-unique) index on confrelid is picked up just by
passing its OID in place of InvalidOid; no syscache involved.
So I went with a full index on pg_constraint(confrelid), which is
declarable today, and pointed the scan at it (one scankey on confrelid,
contype filtered in the loop). That's the attached v1. With the catalog
grown to ~1M not-null rows, ms per ATTACH goes from about 25 ms (growing
linearly) to 0.27 ms and stays flat as the catalog grows; make check is
clean. The cost is that a full index also covers every not-null/pk/check
row, so it is ~6 MB rather than the ~16 kB a confrelid<>0 partial would
be, and adds ~5% to bulk DDL on pg_constraint. That size gap is exactly
what makes the partial version attractive, and exactly what can't be
declared.
Glad to drop it for the trigger-based early-exit instead if you'd rather
not add a catalog index; that route also has the advantage of being
backpatchable, which a catalog change is not.
--
Manu
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-Index-pg_constraint.confrelid-to-avoid-a-seqscan-.patch | text/x-patch | 4.3 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Vlad Lesin | 2026-09-29 16:22:42 | Re: ReplicationSlotRelease() clobbers another backend's statusFlags entry |
| Previous Message | Bharath Rupireddy | 2026-09-29 15:45:17 | Re: parallel autovacuum: Propagate track_cost_delay_timing to parallel workers |