ID |
|
|---|---|
Status |
Backlog |
Bucket |
model |
Priority |
2 |
Theme |
model-cleanup |
Created |
2026-08-21 |
Updated |
2026-08-21 |
intent_field_column_table costs 151 seconds for 116 rows, and it is the inlining
intent_field_column_table takes 151 seconds to return 116 rows against a realistically populated
store, and nothing under it is the reason. Measured by the acceptance dry run of the store-performance
skill: a real capture of the sakila example’s 4111-line schema against the sakila jOOQ catalog, the
relation timed in isolation, then each of nine children timed the same way. The children are all
cheap. intent_argmapping_key_column_candidate is 0.75 s, intent_argmapping_binding_leaf and
intent_field_accessor_hop are about 27 ms, and the other six are between 0 and 7 ms with four of
them empty. So there is no expensive child to reach for and nothing underneath it worth registering,
which is the push-down lever ruled out by measurement rather than by taste.
What the plan says instead is that the cost is the expansion. EXPLAIN ANALYZE over the relation
produces 807 scan nodes and 1.37 MB of plan text, and its scan counts are not one large number but
the same middling number repeated: 5700 twice, then 4148 a dozen times over. That is view inlining
with no common-subexpression elimination, the rule stated on
docs/architecture/explanation/fact-model.adoc under "Derived reads are views, not stored facts",
arriving as a plan. It is also what the relation’s static multiplicity predicts: the
report-inline-multiplicity ranking puts it fifth in the schema at 533 instantiations per read.
Nothing reads this relation at build time in a way that pays the 151 seconds today, which is why the
cost is not currently visible. That makes it the same shape as the hop-column registration’s own
history: a cost that arrives with its first real reader, and a lever best pulled in the increment
that adds one rather than left as a surprise for it. The question this item has to answer is which
lever, in the order the page states. Whether the subject itself earns a meta_materialize
registration turns on its reader count and the price of its refresh, and the refresh here is one
evaluation of a 151-second view, so that trade is not obviously winnable and wants measuring rather
than assuming. Whether some part of the derivation should instead become a captured fact, the top
rung, has not been looked at at all.
One thing to know before theorising: the 533 is already net of the registry. Two of this relation’s
children are registered and therefore tables at read time, intent_argmapping_pair named on fifty
lines of the view body and intent_spelled_table on three, and the multiplicity report exempts a
materialized relation by construction, a table’s subtree being itself. So the breadth the ranking
reports is live breadth and the 151 seconds is not waiting on a registration that already exists.
Which means the first move is attribution rather than a lever: read the plan’s scan nodes back to the
relations they belong to, so the 151 seconds is apportioned across the tree instead of guessed at.
The repeated 4148 is the number to explain first, since fifty scans of a thirty-nine-row table is not
it.
Found by, not caused by, the store-performance skill’s dry run. Filed separately rather than widened into that item, whose subject is the methodology.
A live reader has since turned up, which moves the premise above
Added by the session that filed the diagnostics-drain budget-overrun item, since shipped. The claim
that nothing reads this relation in a way that pays the cost today is no longer safe. The language
server’s diagnostics drain reads it, in the one statement DiagnosticFacts issues per graph, and on a
real dev session that statement overran its 30 s budget and was aborted, so a developer’s diagnostics
stopped tracking their schema. That is the "first real reader" this item’s own reasoning says the lever
should be pulled for, and it is not a build-time reader, which is why the build’s timings did not see
it coming.
What it does not establish is that this relation is the expensive term in that statement. The drain reads it filtered by graph and by explicit type-field pairs, unlike the 151-second unfiltered measurement, and the statement also carries the census-side join shape that the language-server latency item, since shipped, measured at about 1.1 s per evaluation on this store. Attribution across that statement’s roughly twenty subqueries belongs to the drain’s own item; what belongs here is that the reader now exists and the cost has somewhere to land.
The drain item has since done the attribution, and most of this item’s premise moved
Added by the session that implemented the diagnostics-drain budget-overrun item, whose write-up
carried the measurements and whose changelog entry now carries the summary. Confirmed here: the drain’s read of this relation paid the
full unfiltered evaluation, 131 s on a comparable box, because the filters never prune past the
ROW_NUMBER. The 4148-style repeated scans this item said to explain first were attributed:
sql_constraint_column under the reference-step machinery, re-entered once per naming and once per
row of the unresolved-path arm’s correlated NOT EXISTS against intent_field_column_scope, on top
of intent_resolved_type_binding being re-evaluated per naming through its own COUNT(*) OVER.
Two registrations landed there rather than here, because the drain was the live reader:
intent_resolved_type_binding and intent_field_column_scope are now meta_materialize rows whose
reason columns carry the arithmetic. One evaluation of this relation is now about 144 ms on the
same capture, from 131 s. What remains for this item is the smaller question of whether that
residual, and this view’s remaining static breadth, still earn work of their own, and the answer
should start from fresh numbers rather than from the 151 s in the title.