compute: concatenate row bytes for identity join closures - #38670
compute: concatenate row bytes for identity join closures#38670frankmcsherry wants to merge 2 commits into
Conversation
When a linear join stage's closure is the identity, the output row is the key, the stream value and the lookup value in that order. Build it by concatenating the three row encodings instead of decoding them into datums and packing them again. ExtendDatums gains as_row_ref so the join can ask each side for its row bytes, falling back to the datum path when a side is not stored as one row encoding. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mz_join_core consolidates each chunk of results before emitting it, which pays off when the closure can map distinct input pairs to equal results. An identity closure cannot, so let the renderer tell the join core whether to consolidate, and pass false on the identity path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
QA LLM Review1. MEDIUM -- Skipping join output consolidation lets the join core's own cancelling matches escape
The identity path passes Details
Concrete case: for key Results stay correct, since downstream arrangements consolidate; the cost is throughput and peak memory on exactly the wide fan-out joins this change targets. A dynamic guard would keep the measured win without giving up the cancellation: during hydration |
When a linear join stage's closure is the identity, its output row is the key followed by the stream value and the lookup value, in that order. The renderer still built each output row by decoding all three into datums and packing them again. This change concatenates the three row encodings directly when all three are available as row-encoded bytes, and falls back to the datum path otherwise (a dictionary-coded arrangement does not expose contiguous row bytes).
ExtendDatumsgainsas_row_ref, defaulting toNone, implemented forRow, references, and the row spine'sDatumSeqwhen no codec is present. The join uses it in a newis_identitybranch ahead of thecould_errorsplit; an identity closure cannot error, so that branch returns no error collection.Hydration of
SELECT t.k, t.v, d.w FROM fact t JOIN dim d ON t.k = d.kindexed byk, 10M fact rows, 1M dim rows, one worker, three runs each, median:JoinoperatorThe other operators in the dataflow are unchanged. Measured on a local build with the other hydration changes in flight (#38660, #38663, #38665, #38668, #38669) applied, which is why the baseline is below
main; the join operator's share is independent of those.Existing join coverage in sqllogictest and testdrive exercises both branches; no new tests, since the output is byte-identical by construction.
Second commit: no output consolidation for identity closures
mz_join_coreconsolidates each chunk of produced results before emitting it, which pays off when the join closure can map distinct input pairs to equal results, as a projection that drops columns does. An identity closure cannot: distinct(key, left value, right value)triples concatenate to distinct rows, so the sort behind that consolidation, about ten row comparisons per output row, recovers nothing.The second commit threads a
consolidate_outputflag from the linear join renderer into the join core. The identity-closure path passesfalse; every other path keeps consolidating. The differential join implementation never consolidated and is unchanged.Hydration of
SELECT t.k, t.v, d.w FROM fact t JOIN dim d ON t.k = d.kindexed byk, 10M fact rows and 1M dimension rows, one worker, median of three, on a local build with the other hydration PRs in flight applied:Joinoperatorjoins.sltandjoin-identity-elision.sltpass; the output is the same multiset either way, since the downstream arrangement consolidates.