perf(postgres): group catalog rows once in loadTables() - #12746
Open
irontaek wants to merge 1 commit into
Open
Conversation
Code Review by Qodo
1. A performance comment repeats the diff
|
alumni
self-requested a review
August 26, 2026 18:40
loadTables() re-scanned the full catalog arrays for every table: all columns,
all constraints (three times), all foreign keys and all indices. One scan sat a
level deeper still, walking every constraint again for each column of the table.
Cost grew as tables x rows.
Group the rows into lookup maps once, right after the catalog queries return,
and replace each per-table scan with a map lookup. Predicates are unchanged --
the same table/schema/column/constraint-name comparisons decide membership, they
just decide it once per row instead of once per (table, row) pair.
Measured on a faithful extraction of the current filter structure, median of
repeated runs, results compared for equality at every size:
tables columns before after
5 100 0.036ms 0.024ms 1.5x
20 400 0.410ms 0.096ms 4.3x
50 1,000 2.40ms 0.244ms 9.8x
100 2,000 10.2ms 0.450ms 22.7x
300 6,000 77.7ms 1.41ms 55x
800 16,000 630ms 4.5ms 140x
Small schemas are not penalised -- grouping is already cheaper at 5 tables.
Closes typeorm#12745
irontaek
force-pushed
the
perf/postgres-load-tables-grouping
branch
from
September 12, 2026 06:12
70ecf94 to
d74b62d
Compare
Author
|
Friendly ping — this groups the flat catalog rows once in I rebased it onto current master today, so it is mergeable again and the title check is green. Glad to answer questions or split it if the diff is easier to review in pieces. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of change
Closes #12745.
loadTables()re-scans the full catalog arrays once per table. After the four catalog queries return,dbTables.map(...)walks every table and runs a full.filter()overdbColumns, overdbConstraints(three times, for UNIQUE / CHECK / EXCLUDE), overdbForeignKeysand overdbIndices. One scan sits a level deeper still: for every column of the table it walks all ofdbConstraintsagain to find that column's constraints.This change groups the rows into lookup maps once, right after the catalog queries return, and replaces each per-table scan with a map lookup.
The predicates are unchanged. The same
table_schema/table_name/column_name/constraint_namecomparisons decide membership — they just decide it once per row instead of once per (table, row) pair. Where the original filtered onconstraint_namealone (uniques, foreign keys), the grouping key isconstraint_namealone too, so that scoping is preserved as-is. Composite keys are joined with ``, which a Postgres identifier can never contain.loadTables()runs onsynchronize, on migration runs and on schema introspection, so this shows up as start-up / deploy latency on larger schemas.How I verified it
A faithful extraction of the current filter structure (same predicates) run against synthetic catalog rows of 20 columns / 8 constraints / 3 FKs / 4 indices per table. Both versions run on the same data, the results are compared for equality at every size and the script throws on any mismatch, then each is timed as a median of repeated runs — before/after crossed twice so JIT and thermal drift cannot favour one side.
Small schemas are not penalised. Building the maps is already cheaper than the scans at 5 tables, so there is no break-even below which this would be a regression — that was the first thing I checked, since
Mapconstruction has a fixed cost that can lose to a short scan.Also ran
tsc --noEmitandprettier --checkon the file.What I did not change
loadTables()of cockroachdb, aurora-mysql, mysql, sqlserver, sap, oracle and spanner. I kept this to one driver so it stays reviewable, and I am happy to follow up per driver if the direction looks right..map,.filter,.length, andOrmUtils.uniq, which builds a new array viareduce) — I checked each one. If you would rather not rely on that, I can copy on read at the cost of the allocation.Checklist
The existing schema tests need a live database, so I relied on CI for them plus the equality check above. Happy to add a unit test around the grouping if you would like one.
Reproduction script