CF 7365 v6 -- counts on master dfd7ede529f, without and with v6-0001 Counts only, no times: pg_stat counters and user-space instructions (perf stat -e instructions:u). Builds: gcc -O2 -g, no cassert. Three runs per side, alternating order; every run is listed. All numbers come from repro-counts.sh.txt (attached), run on 26 Oct 2026. 1. pg_upgrade of pg_upgrade_catalog_repro.sql (David's script) Old cluster built with master; new cluster is master or v6. The script's arguments are scaled down; max_locks_per_transaction = 4096 on both clusters. Instructions cover pg_upgrade and every process it starts (both servers, pg_dump, pg_restore). Scan counters are read in the restored database right after the upgrade. scale 1/10: build_catalog_bloat(32, 100, 10, 397, 200) 3,200 partitions; 95,756 pg_constraint rows (n 85,947, p 3,295, c 3,234, f 3,232, u 48) pg_constraint confrelid_index instructions (billions) seq_scan idx_scan run 1 run 2 run 3 master 3200 - 187.80 187.54 187.77 v6 0 3200 100.15 100.15 100.15 scale 2/10: build_catalog_bloat(64, 100, 10, 794, 200) 6,400 partitions; 191,316 pg_constraint rows master 6400 - 556.21 556.20 556.23 v6 0 6400 206.57 206.62 206.60 master / v6: 1.87x at 1/10, 2.69x at 2/10. From 1/10 to 2/10, v6 grows 2.06x and master 2.96x. The full scale (957k rows) was not run. One seq scan per ATTACH PARTITION on master: 3200 and 6400 are the partition counts. Old cluster on 18.6 instead (1/10, one run each): to master, 3200 seq scans and 378.86 billion instructions; to v6, 0 seq scans, 3200 index scans and 291.49 billion. After every v6 upgrade, from master or from 18.6: bt_index_parent_check(heapallindexed) on pg_constraint_confrelid_index and bt_index_check(heapallindexed) on all 6 pg_constraint indexes: ok. 2. heap_truncate_find_FKs(): TRUNCATE and ON COMMIT DELETE ROWS In the 1/10 cluster after the upgrade (95,759 pg_constraint rows). One single-user backend per workload under perf stat; each workload runs its statement 100 times, one transaction each. The controls do the same work without reaching the FK lookup. seq_scan rows read instr. (millions) run 1 run 2 run 3 TRUNCATE pk, fk (fk references pk) master 100 9,575,900 2915.4 2915.4 2915.1 v6 0 0 203.1 203.0 203.0 control: TRUNCATE of a table with no triggers master 0 0 122.1 121.8 121.7 v6 0 0 122.0 121.5 121.7 INSERT into an ON COMMIT DELETE ROWS temp table with a trigger master 100 9,575,900 2817.3 2817.3 2817.5 v6 0 0 84.2 84.3 84.5 control: same temp table, no trigger master 0 0 80.8 80.8 80.7 v6 0 0 80.7 80.7 80.7 Above the control, per statement: TRUNCATE 27.9M instructions on master, 0.81M on v6; commit 27.4M on master, 0.04M on v6. 3. Write cost of the index: NOT NULL constraints Fresh initdb, single-user backend: CREATE TABLE of 5 tables x 200 NOT NULL columns, minus the same tables with nullable columns. instructions for 1000 NOT NULL constraints (millions) run 1 run 2 run 3 master 151.87 151.93 151.97 v6 159.58 159.54 159.43 About 7.6k more instructions per NOT NULL constraint (+5.0%); 7.5k to 7.7k when each run is paired with the master run of the same number. 4. From the 15 Oct runs on v4 (same code as v6, other hunk offsets) WAL when creating constraints: +1% to +4%, about 120 bytes per pg_constraint row (WAL bytes are exact). Index size: 16 kB per database after initdb; 6.3-6.8 MB next to a 342 MB pg_constraint with about 1M constraints. pg_upgrade from master with 820 constraints of all kinds: amcheck (heapallindexed) clean on all catalog indexes. 5. v6-0002 (regression test), mutation check Reverting one call site at a time to a heap scan (InvalidOid) makes stats.sql fail on exactly that site's column: heap_truncate_find_FKs t t f t CloneFkReferenced f t t t ATPrepChangePersistence t t t f GetParentedForeignKeyRefs t f t t make check with v6 (cassert): 239/239. Not measured: wall-clock time of pg_upgrade at any scale.