Repository navigation
DDIR: half_join, on the corgi backend - #937
Open
frankmcsherry wants to merge 3 commits into
Open
frankmcsherry wants to merge 3 commits into
frankmcsherry wants to merge 3 commits into
Conversation
frankmcsherry
force-pushed
the
ddir-half-join
branch
from
October 16, 2026 03:12
506d217 to
e373395
Compare
`HalfJoinBatcher` holds a lifted stream's updates in a `MergeLadder` of chains in time order, sorted and consolidated by corgi. An update is released once the arrangement's frontier unblocks it: its time is before the frontier's least element in the total order, or equal to it when strict. Release takes whole chunks from the front of each chain and part of at most one more, and arrivals already unblocked skip the ladder. The bound the batcher reports is the minimal times of what it keeps, in the partial order, cached per chunk. What one extraction releases leaves as runs of at most 64K rows, each sorted by identifier within one window of its source. `CorgiHalfJoin` merges a unit's runs a block at a time, below one identifier horizon per block, so each run is read front to back. Each block meets every arranged chunk from where the previous block left it, so a chunk is walked at most once per unit. A pair meets when the arranged time precedes the update's in the total order, and gives the projection of `(key, val, v1)` with the data time `s ∨ t2`, at the update's time and capability. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`stream | half_join(other, strict, (k, v, v1) -> (k', v'))` meets each update of a lifted stream, `(k ; (v, time))` at its own time `t`, with `other`'s rows at its key whose times precede `t` in the total order on times (strictly when `strict` is 1, or also equal when 0). Each pair gives `(k' ; (v', time ∨ t2))` at time `t` with the product of the diffs; the lambda sees no times, and `other`'s changes produce nothing. The operator is dogs3's `half_join_with_tactic`, which owns progress, capabilities and yielding, with the corgi batcher and tactic. The arrangement compacts to the stream frontier stepped back one in each coordinate. The vec backend and explain do not support it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`triangles_bench` counts triangles in a random graph by binary joins, which arrange the two-paths, or by a delta query of half joins, which arranges only the edges. It reports the time and memory to load the graph and the time per tick as edges are replaced, and counts heap allocations to report the heap's peak. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frankmcsherry
force-pushed
the
ddir-half-join
branch
from
October 16, 2026 17:06
e373395 to
044035c
Compare
This branch has not been deployed
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.
Half joins for DDIR, on the corgi backend, following dogs3 and Materialize's delta joins.
Builds on #938, whose
MergeLadderthe batcher uses.Surface
stream | half_join(other, strict, (k, v, v1) -> (k', v'))(v, time), asliftmakes it;timeis a time as data.tmeetsother's rows at its key whose timest2precedetin the total order on times: strictly whenstrictis 1, or also equal when 0.(k' ; (v', time ∨ t2))at timet, with the diffs multiplied. The lambda sees(k, v, v1)and no times.other's changes produce no output.A delta join is two half joins and a
delay:Commits
corgi/half_join/), unused until the next commit.Implementation
The operator is dogs3's
half_join_with_tactic, which owns progress, capabilities and yielding.corgi/half_join/supplies the two pieces it takes, which meet only atRun.HalfJoinBatcher(batcher.rs):MergeLadderof chains in time order, asTimeChunks: rows(t, key, (v, s)), sorted and consolidated by corgi (sort_blocks, merged withsurvey_groups).Runs of at most 64K rows, each sorted by identifier within one window of one source; each source is dropped once its runs are formed.CorgiHalfJoin(tactic.rs):corgi/join.rsdoes), so runs are read front to back.time ∨ t2as a lane-wise max, and grouping by the capability covering each row.backend/corgi.rsplaces stream updates withCorgiPact, retimes imported batches asRetimeJoindoes, yields after 1M records as Materialize does, and compacts the arrangement to the stream frontier stepped back one in each coordinate (saturating).The vec backend panics on
half_join, and explain does too, as forlift.Also: a comment in
parse/mod.rscited avalidate_lift_itercheck that does not exist; it now says nothing checks that discipline.Measurements
cargo run --release --example triangles_bench -- [join|delta] 1000000 100000 1000 20 [workers]: 1M random edges over 100K nodes, then 20 ticks each replacing 1,000 edges, withcodegen-units = 1and runs interleaved.Memory after load is the process's footprint.
The binary-join rows come from an earlier round of runs than the delta-query rows.
Against this PR's first batcher and tactic, the delta query is about 10% slower to load at 1 worker, at par at 4 workers, 31% faster per tick at 4 workers, and lower in peak heap at both.
At 1 worker about 0.5 s of the load sorts each block's rows from its runs, and about 0.3 s forms the runs.
Tests
tests/half_join.rs, at 1 and 3 workers, after each of 24 ticks of pseudo-random additions and retractions: the delta join above equalsa | join(b, …), and reachability computed by half joins in an iterative scope equals reachability byjoin.Mutations that each break a test: both half joins strict; ignoring the time order; dropping the time join; flipping strictness in the batcher, for arrivals or for chunks; a seek that skips a key straddling two blocks; a merge that drops one side's diff; a bound from first chunks only, or stale after a partial release; a wrong chunk skip; a lost released chunk or arrival; a wrong split of an arrival; a lossy time lane; data or update times read from the wrong run; a skewed block end.
Compacting the arrangement to the stream frontier rather than a step behind it makes the iterative reach test hang.
A run out of identifier order also hangs the merge, so a debug assertion checks each run's order.
cargo test --workspacepasses, and the workspace builds without warnings.Not here
🤖 Generated with Claude Code