Repository navigation
Generative JOIN tier and a JOIN bench workload (J8a, part of #1113) - #1165
Merged
mmmries merged 3 commits intoOct 19, 2026
Merged
Conversation
…inst their own JOIN query, and J2's scenario with its per-fact control (J8a, part of salesforce-misc#1113) JOIN transforms had deterministic interleaving tests (J3) but no generated programs and no way to measure the design against salesforce-misc#1091 J2 through the real engine. Generative: generate::join draws one to three LEFT/INNER JOIN definitions over two tables (one- or two-column key; grouped by the joined side, both sides, or the FROM side; COUNT/SUM/AVG either side, an expression argument, composed fields) and insert/update/delete streams on both tables with NULL keys, groups and values, in spread, hot-key and fan-out key shapes. The concurrent runner issues them in bursts over lanes against several drain workers, so both sides race into the same batches and keys. The oracle is the definition's own JOIN ... GROUP BY query, byte for byte; every definition must still be live at the end. The backend, oracle and coverage lose their JOIN unreachables. ConcurrentPlan::settle_after_install lets the run wait for definitions to go live first: a JOIN builds only over empty tables until J4 (salesforce-misc#1109). TRUNCATE is not drawn until J5 (TODO salesforce-misc#1110). The property is #[ignore]d; a hand-built case and a red control run in verify. Benchmark: `join` runs J2's D1/D2 x S1/S2 a-side and b-side pages and its hot-key run, each page one commit, against B0, a per-fact copy kept by the writer and aggregated by Trellis, every page on every variant in rotating order. Oracle and cross-database checks fail the run; a summary line says whether D1 kept S1 < S2 < B0 per page kind. On disk at 2M facts it does on the b-side but not the a-side, where S2 slows with more drain workers (filed as salesforce-misc#1155). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erges (salesforce-misc#1113) With a dozen keys or fewer, every page of a split batch names each key on both sides, so the P and M cell locks order concurrent pages by themselves and a page that skips the J lock still converges: 60 cases of the tier (including runs that widened the race window by 30 and 200 ms) never caught J3's drop-the-J-lock mutation. The wide shape spreads both tables over 64 keys with bursts past the split threshold, so two workers' pages of one batch change a key from opposite sides without sharing a cell. The mutation now diverges by the eighth case; the real engine passes 80 cases (26 wide). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ince the zero-group GC now removes them after it G5's test expected the recomputed zero groups to still be in the hidden table after settle(); G2b's zero-group GC deletes them once the drained prefix passes their stamp. Each PR was green alone. The assertion that the basis refused the insert's page now runs before the drain; the oracle check after settle is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.
Part of #1113 (J8a).
Summary
The generative and benchmark tiers for
JOINtransforms, first part:generative/tests/join_convergence.rs,generative/src/generate/join.rs): two-table programs (ja,jb) with one to threeLEFT/INNERJOIN … GROUP BYdefinitions (single- or two-column key; grouped by the joined side as J2's S1, by both sides as S2, or by theFROMside;COUNT/SUM/AVGon either side, an expression argument, composed fields), and insert/update/delete streams on both tables withNULLkeys, groups and values. Four key shapes:spread(12 keys),hot_key,fan_out(3 keys) andwide(64 keys, bursts always past the 256-row split threshold). Issued through the concurrent runner (bursts of 40-600 ops over 2-4 lanes, 2-6 drain workers, 20-200 ms seals), so writes to both tables race into the same batches and onto the same keys. The oracle is the definition's ownJOIN … GROUP BYquery, compared byte for byte (oracle::render_join_select); every definition must still beliveat the end. The property is#[ignore]d like the others; run it withcargo test -p generative --test join_convergence -- --ignored. Two fast tests run inverify: a hand-built case end to end, and a red control (a corrupted target cell diverges).bench join(benchmark/src/streaming/join.rs): Research: JOIN transforms (FROM a [LEFT|INNER] JOIN b ON … GROUP BY …) kept exact without per-row state, as the replacement for relationships #1091 J2's scenario through the real engine, each page one commit, cost = write time + time until a token taken after it converges: D1 (2,000 facts/key) and D2 (10 facts/key), S1 and S2, a-side pages (J2's 30/40/15/15 mix in 200-change groups) and b-side pages (UPDATE gb, unmatched→matched inserts, matched→unmatched deletes), and J2's hot-key run (8 writers on 10 keys, with and without a concurrent dimension writer). The same-session control is J2's B0: next toa, the application keeps a per-fact copya0carryinggb/yin the writing transaction (J2's B0 apply, rewriting every fact of a key on a dimension change) and Trellis aggregatesa0both ways, every page applied to every variant in a rotating order. Each dataset ends with oracle checks and a cross-database check (eachJOINtarget equals the B0 target with itsGROUP BY); a summary line says whether D1 kept J2's ordering S1 < S2 < B0 per page kind. Runnable as.claude/scripts/bench join ….Not in this part:
TRUNCATEof either side (halts the JOIN until J5 #1110; TODO in the generator and test doc), rows written before define (the JOIN build is J4 #1109; the run waits for every definition to goliveover empty tables before its first op,ConcurrentPlan::settle_after_install). Nightly is disabled project-wide, so no workflow change; the tier runs through the same entry as the other properties.Design notes
JOINunreachables are replaced where the tier needs them:render_definitionrenders aJOIN,ManualBackend::snapshotreads aJOINtarget by itsGROUP BYcolumns,oracle::checkroutes aJOINto its own query with no evaluator leg, coverage counts it asJoinand doesn't readjb.yas a relationship path.ConcurrentPlan::settle_after_install(off for every other tier) is the one change to the shared runner.JOIN … GROUP BYfrom the AST. The only shared piece is the literal renderer (0as0::numericinCOALESCE(SUM(jb.y), 0)).wideexists. With 12 keys or fewer, every page of a split batch names each key on both sides, so thePandMcell locks order concurrent pages anyway and theJlock is never the only thing between them. J3's mutation checks, run against this tier: dropping theLEFTflip term or usingP_newforP_olddiverges on the first case (and in the hand-built case); dropping theJlock did not diverge in 60 cases of the three dense shapes, even with a 30 or 200 ms sleep widening the race. Withwideit diverges by the eighth case, and the unmutated engine passes 80 cases (26 wide).Verification
Generative tier (logbook ids):
20261018T001407-b-sweep20261018T030141-b-sweep20261018T041104-b-sweep20261018T050035-b-sweepwideon the real enginewidecases, 52 batches split across workers in 41 of 80 casesPlus two fast tests in
verify: a hand-built case converges, and a corrupted target cell diverges (red control).bench join(logbook ids20261018T001431-b-benchtmpfs smoke at 100k facts;20261018T025034-b-benchfailed in the hot-key phase on "too many clients", fixed by running only the measured variant's client there;20261018T033430-b-benchthe run below plus the 1-worker diagnostic).bench --disk join, 2M facts, 8 drain workers, 10 ms seals, all oracle and cross-database checks passed. Medians,page_s:UPDATE gb(20 rows)D2 (10 facts/key): a-side S1 1.14 s, S2 3.41 s, B0 4.66 s. b-side S1 0.05-0.06 s, S2 0.09-0.17 s, B0 0.06-0.59 s. Hot keys (8 writers, 160k changes on 10 keys), converged changes/s: S1 10.6k, S2 8.0k, B0 49.7k without a dimension writer. With one: S1 16.0k, S2 6.4k, B0 3.0k (B0's own rewrites deadlocked 53 times against the fact writers, retried).
D1's ordering is reproduced on the b-side but not on the a-side. S2's a-side page costs 2.14 s with one drain worker but 7.27 s with eight (same pages, a separate run). B0 wasn't run with one worker, so whether S2 < B0 holds at one worker is open; against B0's 8-worker 2.63 s, S2's 1-worker page is 1.2x faster, where J2 measured 3.6x. Filed as #1155 (Requires Design Decision, Next Release): a JOIN over few keys gets slower with more drain workers. The bench reports the ordering per page kind (
d1_orderingin its summary line). It is a measurement, so it doesn't fail the run.How to read these numbers:
GROUP BY) overa0, as J2's B0 did, while eachJOINvariant keeps one. That favours S1 and S2, so the a-side inversion is not caused by the control. The b-side margins are partly B0 paying for two targets.JOINs are 5-6x behind B0. The hot-key runs used 8 workers only, so they don't show whether that gap scales with workers the way #1091 JOIN apply: a-side pages slow down as drain workers are added (D1 S2: 7.3 s with 8 workers, 2.1 s with 1) #1155's a-side does.UPDATE gbpages: B0 0.060 s, S1 0.062 s, S2 0.088 s (J2: S1 0.07 s < S2 0.64 s < B0 1.0 s). The acceptance criterion is D1 only.verify: VERIFIED, passed=3558 failed=0 ignored=40.🤖 Generated with Claude Code