Skip to content

perf: read native records directly and shorten the compile tail - #1139

Merged
kvlonge merged 7 commits into
mainfrom
perf/native-records
Oct 18, 2026
Merged

kvlonge merged 7 commits into
mainfrom
perf/native-records

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Why

After the graph-phase changes, a cold dense 10k compile still spent long stretches on the main thread building and reading Python records the native side already held, and its last seconds ran on one core: JSON text was re-copied at every nesting level, semantic coverage was computed twice, artifact groups were written strictly after they were queued, and the process freed the whole project graph just before exiting.

Changes

  • Compile-phase consumers read native model inputs, compiled models, model files and source views directly; analysis-session rows, declared relations and source keys come from native facts; record helpers find specs by type.
  • The JSON emitter appends into one buffer; compile JSON semantic coverage is computed once.
  • Model, function and audit artifact groups are written by one background writer while the next group is queued; earlier write errors are still reported first and interrupts stop the writer between groups.
  • The launched CLI leaves compile state for process exit (gc.freeze) after all writes; in-process entries free as before.
  • fensu Python-crossing budgets lowered.

Verification

  • Compiled SQL, compile JSON and target artifacts identical (1k and 10k); 1k cold/warm/edit cache counters identical.
  • Dense 10k, 4 paired cold rounds vs main: median wall 27.2 s to 24.3 s, instructions 402 G to 386 G (-3.9%), graph_ms 14.3 s to 12.9 s.
  • Focused tests for records, target writer, staged publication, launched teardown and new write-failure and interrupt cases pass.
  • Independent review and follow-up: findings fixed, no blockers.
  • make check-ci passes.

Compile-phase consumers (analysis session requests, declared relations, source keys, lineage counts, SQL test assembly, scope index, model loop) now read native model, source and compiled-model records directly instead of materialising Python record objects. Generic record helpers resolve specs by type pointer. Output is byte-identical; main-thread instructions on the dense 10k cold compile drop by about 10G (13.5%).
Serialising the 85 MB dense compile report copied every nested container's
text into its parent and allocated a string per escaped character. Thread one
buffer through the emitter instead; the output is byte-identical.
The --json report computed the selected models' semantic coverage twice: once
for the stderr notice and again for semantic_checks_partial. Compute it once
and hand the notice to the caller before the report is assembled.
write_static_target alternated between queueing a group on the main thread
and waiting for its files to be written. Model, function and audit groups now
go to one writer thread in order while the next group is queued; the SQL test
group's synchronous flush waits for them, so records, stale cleanup and the
manifest still see every file on disk. The writer stops at the first failure
and no later group is written.
The launched sqlbuild-python process exits right after its command, yet each
compile spent about half a second freeing the project graph and collecting it
during interpreter finalization. The launcher now asks compiles to keep that
state alive and freeze it out of collections; in-process entries still free
it normally.
When a step after a background group failed, the artifact batch discarded the
writer's outcome on drop, so a later error could mask an earlier OSError and
defeat the staged path's direct-write fallback. Errors from the queueing
section now wait for the writer and report its write error first, as the
synchronous writes did.

Waiting for the writer releases the GIL and checks for signals. An interrupt,
or any non-Exception error, stops the writer after the group it is writing,
skips groups not yet started, and then propagates.
@kvlonge
kvlonge enabled auto-merge (squash) October 18, 2026 08:06
@kvlonge
kvlonge merged commit 98a2133 into main Oct 18, 2026
43 checks passed
@kvlonge
kvlonge deleted the perf/native-records branch October 18, 2026 08:44
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