Skip to content

fix(compiler): keep shared-analysis lineage and stage captures deterministic - #1005

Merged
kvlonge merged 2 commits into
mainfrom
fix/deterministic-analysis-memo
Oct 7, 2026
Merged

kvlonge merged 2 commits into
mainfrom
fix/deterministic-analysis-memo

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Why

CHI-539: compiled-project stage captures differed python-vs-python (seeds 14, 29, 34), and dense 3k with --stage-captures ran out of memory. Native batch sizes depend on timing and each batch shares one string pool, so encoded lineage rows and the memo's cached entries changed with batching. This hid a bug: a member reusing a cached shared analysis lost its own resource names, so sqb lineage --mode fast could name another model's source.

Changes

  • Compact projection keeps the member's resource_names on the cached-template path; the analysis cache version is bumped so wrong entries are recomputed.
  • Captures record CompactLineageFacts as decoded facts and omit the shared_analyses reuse cache; schemas, analysis_shapes, expression_shapes stay unordered.
  • Containers and strings of 1 KiB or more are stored once under the SHA-256 of their noise-masked JSON, one node per line. Sets sort by the same masked form; mappings using reserved marker keys are encoded as pairs.
  • The harness compares captures on disk: equal digests are skipped, only nodes on a differing path are parsed, and pointers and previews are logical.
  • Tests: lineage across batches, captures under forced schedules, python-vs-python seed 29, masked set noise, marker-shaped data, previews.

Verification

  • New schedule, lineage and masked-set tests failed before the fix.
  • After rebasing onto feat(native): make native discovery the default compiler engine #1003: make compiler-differential with captures and coverage passes 139/139, python vs native and python vs python. Seeds 14–34 pass both ways.
  • Dense 3k, --stage-captures --jobs 1: harness peak RSS 0.93 GB, compile 1.44 GB; captures 0.5 GB per side, compared in 1.5 s with 39 MB.
  • Dense 3k CPU medians, 3 alternating runs (load 9–19, 4 cores): no-cache 22.41 s vs 22.56 s, cache write 32.15 vs 32.11, warm 0.68 vs 0.68. Perf-guard fingerprints (1k, 3k) are unchanged after the rebase and cache bump.
  • make check-ci, make test (11,445 passed) and the hygiene scan pass.

…le-independent

A model that reused another model's shared analysis lost its own resource
names when the template facts were already projected, so fast column lineage
named the representative's source. Pass the member's resource names on the
cached-template path too.

Compiled-project stage captures also depended on how the binding dataflow
batched models: compact lineage facts index a batch-wide string pool, and the
shared-analysis memo keeps whichever representative and lazy caches a schedule
produced. Capture compact lineage as its decoded facts and leave the memo out,
so python-vs-python captures are identical under any schedule.

Bump the analysis cache version so entries written with the wrong names are
recomputed.
Captures embedded every repeated subtree in full, so their size grew
quadratically with sources per file and the harness held and parsed both
engines' captures in memory; dense 3k ran out of memory.

The capture writer now stores every container or string of at least 1 KiB
once under the SHA-256 of its compact JSON, with invocation ids and native
store suffixes masked, and refers to it as {"__shared__": digest}. Sets and
unordered maps sort by the same masked form, so ordering never depends on
noise the comparison ignores, and authored mappings using a reserved marker
key are encoded as pairs. Each shared node and the root sit on their own
line, so the harness indexes a capture by digest, treats equal digests as
equal without reading them, and parses only the nodes on a differing path
while still reporting the logical JSON pointer and resolved previews.
Captures stay on disk, one directory per compared side, instead of in memory.
@kvlonge
kvlonge enabled auto-merge (squash) October 7, 2026 12:46
@kvlonge
kvlonge merged commit 1175b83 into main Oct 7, 2026
32 checks passed
@kvlonge
kvlonge deleted the fix/deterministic-analysis-memo branch October 7, 2026 13:10
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