Skip to content

perf(planner): read fingerprint state once and keep plan generation linear - #904

Merged
kvlonge merged 8 commits into
mainfrom
perf/chi-460-planning-state
Oct 1, 2026
Merged

kvlonge merged 8 commits into
mainfrom
perf/chi-460-planning-state

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Why

Planning time grew faster than project size: at 10k models plan generation took 9-13 s and warehouse inspection about 16 s on a synthetic DuckDB project, and a real Snowflake plan read the fingerprint state table twice (CHI-460).

Changes

  • Fingerprint state is read once per full plan (or any plan with migrate_from) and shared with migration inspection; narrow selections keep the filtered read.
  • Statement monitoring: no per-statement monitor thread unless a statement runs past the heartbeat threshold or the adapter polls query IDs; events validated once; SQL hashed once; finished statements are released immediately.
  • Identity hash resolution and SQL-test fixture types no longer copy whole maps per node or test.
  • The rules progress line reports the phase's wall time.
  • CI guard: plan inspection and generation must scale near-linearly from 1k to 3k models; unit guard on a 20,000-node graph.

Verification

  • 10k benchmark: inspection ~16 s -> ~11 s, plan generation 9-13 s -> ~4 s (test-heavy 9.05 s -> 3.25 s); plan output byte-identical at 1k/3k/10k.
  • -v, --json and lifecycle events identical apart from IDs and timings; retention test proves finished statements are collectable.
  • Real Snowflake prod plan (read-only): plan generation and inspection equal or faster; totals dominated by machine load.
  • make test, make check-ci; one independent review, findings fixed.

Full-project plans and projects declaring migrations read the latest
fingerprint per identity without a node filter, apply the selection in
memory, and share the rows with migration inspection instead of reading
the state table a second time. Narrow selections keep the filtered read.
Statement monitors without a query-ID provider now start their thread
only once a statement outlives the heartbeat threshold, through one
shared deferred-start daemon, instead of starting and joining a thread
for every statement. Lifecycle events are validated once at
construction rather than again on publish and on sequencing, JSON
freezing builds error paths only on failure, and statements hash their
SQL once. Recorded events, diagnostics, and -v output are unchanged.
Expected and write identity resolution copied the whole hash cache for
every resolved node, which made identity context building and change
reconciliation quadratic in project size. A memoized resolver now
records each node once with the same resolution order, cycle handling,
and results.
SQL-test fixture completion copied the project-wide expected-type map
for every test, which made planning test-heavy projects quadratic. Each
test now overlays only the models of its chain on the shared map.
The rules completion line reported only built-in and custom rule time,
which run concurrently and are not phase wall time, so users could not
see how long the rules phase took next to the compile line. It now leads
with the phase wall time in the same form as other phase lines.
Plan the inspection benchmark at 1,000 and 3,000 models without the
compile cache and fail when warehouse inspection or plan generation
grows more than 1.5 times faster than the project, or when either
phase exceeds a loose ceiling.
… starts

A stopped monitor now cancels its pending deferred start and drops its
callbacks and query-ID provider, so a finished statement and its SQL are
no longer retained until the heartbeat deadline. A monitor thread is
recorded only after it starts, so a refused start no longer makes stop()
join an unstarted thread and fail an otherwise successful statement.
…imings

The rules progress line reads the clock once before rules run, between
the compile and planning windows. The phase-timing test now supplies
that read, gives rules a measurable duration, and proves compile and
planning stay disjoint from it, with and without progress reporting.
@kvlonge
kvlonge enabled auto-merge (squash) October 1, 2026 19:52
@kvlonge
kvlonge merged commit 3b5f131 into main Oct 1, 2026
30 checks passed
@kvlonge
kvlonge deleted the perf/chi-460-planning-state branch October 1, 2026 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant