feat: support co-partitioned range right-side equi hash joins#23484
feat: support co-partitioned range right-side equi hash joins#23484gmhelmold wants to merge 10 commits into
Conversation
|
Done in 852a619: the end-to-end matrix now lives in |
gene-bordegaray
left a comment
There was a problem hiding this comment.
few nits but this is great, thank you. Feel free to ping on another round 😄
| // Same rows as `range_partitioned` but split into only three range | ||
| // partitions on `range_key`. Used to exercise the co-partition check when | ||
| // two Range inputs disagree on partition count. | ||
| let narrow_output_partitioning = Partitioning::Range( |
| NULL 35 350 | ||
|
|
||
| ########## | ||
| # TEST 22: Mark Join Marker Semantics over Range Partitioned Inputs |
There was a problem hiding this comment.
nit: can we move this as the last test in this block?
There was a problem hiding this comment.
Done — moved to the end of the partitioned-hash-join block (now the last test there, before the SMJ/SHJ cases that came in with the main merge).
| physical_plan | ||
| 01)FilterExec: non_range_key@1 = 2 OR mark@3, projection=[range_key@0, value@2] | ||
| 02)--HashJoinExec: mode=Partitioned, join_type=LeftMark, on=[(range_key@0, range_key@0)] | ||
| 03)----RepartitionExec: partitioning=Hash([range_key@0], 4), input_partitions=4 |
There was a problem hiding this comment.
to be clear this should disappear after #23453 ? Just putting here to show reationale on why the unit is good
There was a problem hiding this comment.
Good question — no, this one stays. This PR is the implementation of #23453 (it closes it), and the plan pinned here is already generated with the change applied: the range-satisfaction gate in HashJoinExec::input_distribution_requirements covers Inner | Right | RightSemi | RightAnti | RightMark only, and SQL IN-subquery decorrelation produces a LeftMark join — a left-anchored type intentionally outside this PR's covered set — so its inputs keep the Hash repartition path. And exactly right on the rationale: since RightMark is not reachable from SQL (it only appears when a statistics-based swap flips a LeftMark), its reuse behavior is pinned by the enforce_distribution.rs unit test, while this case pins the mark-marker semantics end-to-end on the SQL-reachable path. It would only change if co-partition reuse is later extended to the left-anchored variants under the #22395 umbrella — happy to file that follow-up if there's interest.
| 30 600 | ||
| 35 700 | ||
|
|
||
| ########## |
There was a problem hiding this comment.
can we add one subset test for sanity check 👍
There was a problem hiding this comment.
Added TEST 22: a RIGHT JOIN on the composite key (range_key, non_range_key) where Range([range_key]) covers only a subset — pins that, unlike aggregates (TEST 3), subset satisfaction stays disabled for partitioned joins, so both sides Hash-repartition on the full key even with the threshold met.
|
Round done! All three nits addressed (mark-semantics test moved to the end of its block; TEST 22 added as the composite-key subset sanity check; rationale for the LeftMark repartition answered inline) — plus the branch is synced with main twice over (absorbed the SMJ/SHJ co-partition tests, #23584's plan changes, and #23623's InterleaveExec cases; full suite green, 98 unit + 44 SLT). Ready for another look @gene-bordegaray 😄 |
gene-bordegaray
left a comment
There was a problem hiding this comment.
sorry, trying to keep files clean and clear. Few more things 👍
| physical_plan | ||
| 01)HashJoinExec: mode=Partitioned, join_type=RightAnti, on=[(range_key@0, range_key@0)] | ||
| 02)--FilterExec: value@1 <= 150, projection=[range_key@0] | ||
| 03)----DataSourceExec: file_groups={4 groups: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_partitioned/part-0.csv], [WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_partitioned/part-1.csv], [WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_partitioned/part-2.csv], [WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch_range_partitioning/range_partitioned/part-3.csv]]}, projection=[range_key, value], output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=csv, has_header=false |
There was a problem hiding this comment.
can we ignore files 👍
| ########## | ||
| # TEST 18: Sort Merge Join Avoids Repartition for Compatible Range Inputs | ||
| # TEST 18: Right Join on Range Partition Column | ||
| # Right-side partitioned hash joins also opt in to Range satisfying |
There was a problem hiding this comment.
lets keep descriptions generic so they remain valid after the PR merges
|
|
||
| ########## | ||
| # TEST 22: Right Join on Composite Key With Range Subset Rehashes | ||
| # The join key (range_key, non_range_key) is a strict superset of the exposed |
There was a problem hiding this comment.
coudl we keep explanations concise 👍
| 50 35 350 | ||
|
|
||
| ########## | ||
| # TEST 25: Mark Join Marker Semantics over Range Partitioned Inputs |
There was a problem hiding this comment.
lets just keep descriptions specific to what this test is showing. Not mentioning other things in teh codebase
There was a problem hiding this comment.
please replace with something like:
# TEST 25: Mark Join Marker Semantics
# Mark joins preserve matched, unmatched, and NULL-key marker behavior over
# range-partitioned inputs.
Use file_groups=<slt:ignore> for the remaining plan assertions so the file no longer pins absolute scratch paths, matching the rest of the file. Trim the TEST 18/22/25 descriptions to be concise and specific to what each test shows, without cross-references or PR-relative wording.
|
All addressed in 04e31b2 👍
Verified locally: |
gene-bordegaray
left a comment
There was a problem hiding this comment.
thanks, one last thing 👍
A descending Range requires descending-ordered split points, so the right input's [30, 20, 10] DESC encodes the same partition boundaries as the left's [10, 20, 30] ASC -- only the sort direction differs. Add a comment so the test's intent (isolating incompatible sort options) is clear to future readers.
|
Good catch that this deserves clarifying 👍 I tried the metadata-only change first, but a descending Range requires descending-ordered split points — So |
gene-bordegaray
left a comment
There was a problem hiding this comment.
These are all non-blockinng.
Thank you for the contribution 🙇 . I kindly ask if you are using AI tools to aid coding to review the comments and output you are getting. It is important to keep comments and documentation clear in the codebase for future readers and maintainability
cc: @gabotechs for when these are fixed
| // Kept as a unit test: `RightMark` join plans cannot be produced from SQL (they | ||
| // only arise when a statistical swap flips a `LeftMark` join around), so this |
There was a problem hiding this comment.
Lets try to keep very PR specific comments out of the code.
Usually phrases like this "Kept as a unit test: ..." does not have any value outside of the scopoe of this PR becuase a new reaader will not have the ocntext that you or I have about this test. Rather lets omit the comments and allow the code to do the talking 👍
| Ok(()) | ||
| } | ||
|
|
||
| // Kept as a unit test: a descending Range input cannot be registered through the |
| // A descending Range requires descending-ordered split points (the constructor rejects | ||
| // ascending points under a descending sort), so [30, 20, 10] DESC encodes the same | ||
| // partition boundaries as the left's [10, 20, 30] ASC -- only the sort direction differs, | ||
| // which is the incompatibility under test. |
There was a problem hiding this comment.
ok I think we got confused here. We are specifically tring to isolate incompatible sort options based on metadata. To isolate this is would be best to use a single split point and just change the descending metadata rather than both 👍
There was a problem hiding this comment.
this test would still fail if the sort otpions check was removed
There was a problem hiding this comment.
And I don't think this warrants a long comments
| NULL 35 350 | ||
|
|
||
| ########## | ||
| # TEST 22: Right Join on Composite Key With Range Subset Rehashes |
There was a problem hiding this comment.
please resplace with something like:
# TEST 22: Composite-Key Right Join Repartitions
# Range([range_key]) does not satisfy a partitioned join on
# (range_key, non_range_key), so both sides repartition on the full key.
| 50 35 350 | ||
|
|
||
| ########## | ||
| # TEST 25: Mark Join Marker Semantics over Range Partitioned Inputs |
There was a problem hiding this comment.
please replace with something like:
# TEST 25: Mark Join Marker Semantics
# Mark joins preserve matched, unmatched, and NULL-key marker behavior over
# range-partitioned inputs.
Remove the 'Kept as a unit test' comments and let the code speak. Isolate the incompatible-sort-options case with a single split point and only the descending flag flipped, and tighten the TEST 22/25 slt descriptions.
|
Thanks for the careful review, and fair point on the comments 🙏 You're right that phrasing like "Kept as a unit test: …" only carries meaning with the context you and I have — a future reader doesn't have it. Stripped those and let the code speak. In
I'll keep comments lean and reader-focused going forward — thanks for pushing on clarity. |
|
could you merge this with origin main. I want to make sure it is correct witht the recent changes in #23454 🙇 |
…ge-copartition # Conflicts: # datafusion/physical-plan/src/joins/hash_join/exec.rs # datafusion/sqllogictest/src/test_context/range_partitioning.rs # datafusion/sqllogictest/test_files/range_partitioning.slt
|
Merged |
|
thank you, this is good now cc: @gabotechs |
gabotechs
left a comment
There was a problem hiding this comment.
Thanks @gmhelmold and @gene-bordegaray!
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #23484 +/- ##
=======================================
Coverage 80.70% 80.70%
=======================================
Files 1089 1089
Lines 368038 368061 +23
Branches 368038 368061 +23
=======================================
+ Hits 297031 297055 +24
+ Misses 53308 53305 -3
- Partials 17699 17701 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Which issue does this PR close?
Partitioning::Rangeinputs for right-side hash joins #23453.Rationale for this change
#23184 let compatible range-partitioned inputs satisfy inner partitioned hash joins without repartitioning. Right-side partitioned equi joins have the same locality guarantee:
Right,RightSemi,RightAntiandRightMarkall anchor every output row on the probe (right) partition (on_lr_is_preservedis probe-side for all four), so when both inputs are co-partitioned by the join keys, execution stays partition-local and the hash repartition is unnecessary.This removes unnecessary
RepartitionExecs for already-co-located inputs, extending the inner-join behavior from #23184 to the right-side variants. No micro-benchmark included, consistent with #23184; correctness is demonstrated by matched/unmatched execution results below.What changes are included in this PR?
The only production change is widening the existing inner-only gate in
HashJoinExec::input_distribution_requirementsfromjoin_type == JoinType::Innertomatches!(join_type, Inner | Right | RightSemi | RightAnti | RightMark)(underPartitionMode::Partitioned). Theco_partitioned/ range-satisfaction machinery from #23184 is unchanged — the sanity checker and enforce_distribution consume range satisfaction join-type-agnostically.One behavior note for #23376: range co-partitioned right joins now stay
Range/Range, so partitioned dynamic filters are disabled for them (has_partitioned_dynamic_filter_routingreturns false), the same safe delta #23184 introduced for inner joins. These variants are probe-not-preserved for pruning, so dynamic filters were not eligible to prune their probe rows regardless.Are these changes tested?
Yes.
enforce_distribution.rs): 4 reuse tests (one per join type) proving compatible range/range inputs keepRangepartitioning with noRepartitionExec; 4 incompatibility tests proving that mismatched split points, sort options, partition counts, or join-key expressions still insert a hash repartition; sanity-check pairs mirroring Support co-partitioned range inner equi joins #23184.range_partitioning.slt):EXPLAINplan pins plus matched/unmatched result checks forRight(incl.NULLleft values),RightSemi,RightAnti, and an incompatible-layoutRightjoin (repartitions, correct results).mainwithout the gate change (verified by reverting the production diff: exactly the 4 reuse tests fail, everything else passes).RightMarktesting note:RightMarkis not reachable from SQL in sqllogictest (IN-subquery decorrelation emitsLeftMark; physicalRightMarkonly appears via a statistics-based swap), so its co-partitioning behavior is pinned at the optimizer/plan level, and mark null-marker semantics (matched/unmatched/NULLbuild keys) are pinned via theLeftMarkpath in the slt.Are there any user-facing changes?
No API changes. Plans over compatible range-partitioned inputs avoid a hash repartition for right-side equi hash joins.