Repository navigation
feat: derive SortMergeJoin sort options from input ordering and allow SMJ for a single partition - #26261
Conversation
0570031 to
97d9ced
Compare
quwin
left a comment
There was a problem hiding this comment.
Thanks for your work @bert-beyondloops!
I ran the focused sort-merge join SQL logic tests against this branch. They currently fail on three EXPLAIN snapshots because the expected plans show a separate ProjectionExec, while the actual plans put the projection on SortMergeJoinExec. I left comments on those snapshots with the plans produced by the branch.
| 01)ProjectionExec: expr=[k1@0 as k1, k2@1 as k2, v@2 as v, w@5 as w] | ||
| 02)--SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)] | ||
| 03)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet | ||
| 04)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet |
There was a problem hiding this comment.
This expected plan seems to be stale. The focused SQL logic run fails the EXPLAIN: DataFusion puts the projection on SortMergeJoinExec, rather than emitting a separate ProjectionExec.
| 01)ProjectionExec: expr=[k1@0 as k1, k2@1 as k2, v@2 as v, w@5 as w] | |
| 02)--SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)] | |
| 03)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet | |
| 04)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet | |
| 01)SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)], projection=[k1@0, k2@1, v@2, w@5] | |
| 02)--DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet | |
| 03)--DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], output_ordering=[k1@0 DESC NULLS LAST, k2@1 ASC], file_type=parquet |
| 01)ProjectionExec: expr=[a@0 as a, b@1 as b, b@3 as b] | ||
| 02)--SortMergeJoinExec: join_type=Inner, on=[(a@0, a@0)] | ||
| 03)----SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | ||
| 04)------DataSourceExec: partitions=1, partition_sizes=[1] | ||
| 05)----SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | ||
| 06)------DataSourceExec: partitions=1, partition_sizes=[1] |
There was a problem hiding this comment.
This single-partition EXPLAIN snapshot seems to fail. The actual plan puts projection=[a@0, b@1, b@3] on SortMergeJoinExec, rather than emitting the expected ProjectionExec.
| 01)ProjectionExec: expr=[a@0 as a, b@1 as b, b@3 as b] | |
| 02)--SortMergeJoinExec: join_type=Inner, on=[(a@0, a@0)] | |
| 03)----SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | |
| 04)------DataSourceExec: partitions=1, partition_sizes=[1] | |
| 05)----SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | |
| 06)------DataSourceExec: partitions=1, partition_sizes=[1] | |
| 01)SortMergeJoinExec: join_type=Inner, on=[(a@0, a@0)], projection=[a@0, b@1, b@3] | |
| 02)--SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | |
| 03)----DataSourceExec: partitions=1, partition_sizes=[1] | |
| 04)--SortExec: expr=[a@0 ASC], preserve_partitioning=[false] | |
| 05)----DataSourceExec: partitions=1, partition_sizes=[1] |
quwin
left a comment
There was a problem hiding this comment.
One follow-up from checking the suggested changes locally:
| 01)ProjectionExec: expr=[k1@0 as k1, k2@1 as k2, v@2 as v, w@5 as w] | ||
| 02)--SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)] | ||
| 03)----SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | ||
| 04)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] | ||
| 05)----SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | ||
| 06)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] |
There was a problem hiding this comment.
This fallback case has the same projection mismatch.
| 01)ProjectionExec: expr=[k1@0 as k1, k2@1 as k2, v@2 as v, w@5 as w] | |
| 02)--SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)] | |
| 03)----SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | |
| 04)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] | |
| 05)----SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | |
| 06)------DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] | |
| 01)SortMergeJoinExec: join_type=Inner, on=[(k1@0, k1@0), (k2@1, k2@1)], projection=[k1@0, k2@1, v@2, w@5] | |
| 02)--SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | |
| 03)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/left.parquet]]}, projection=[k1, k2, v], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] | |
| 04)--SortExec: expr=[k1@0 ASC, k2@1 ASC], preserve_partitioning=[false] | |
| 05)----DataSourceExec: file_groups={1 group: [[WORKSPACE_ROOT/datafusion/sqllogictest/test_files/scratch/sort_merge_join_sort_options/right.parquet]]}, projection=[k1, k2, w], file_type=parquet, sort_order_for_reorder=[k1@0 ASC, k2@1 ASC] |
| DROP TABLE r_asc; | ||
|
|
||
| statement ok | ||
| reset datafusion.execution.target_partitions; |
There was a problem hiding this comment.
Could we restore the test runner’s initial value of 4 explicitly here and in sort_merge_join.slt?
RESET restores the machine default instead. After correcting the snapshots, both files fail with target_partitions: 4 -> 8 on my machine.
| reset datafusion.execution.target_partitions; | |
| SET datafusion.execution.target_partitions = 4; |
97d9ced to
63b9bce
Compare
|
I applied a patch built on top of the 55.1 release. Since then, SortMergeJoin on main has gained embedded projection support, so I updated the affected explain plans to reflect that change. |
63b9bce to
a9d122f
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #26261 +/- ##
==========================================
- Coverage 82.83% 82.79% -0.04%
==========================================
Files 1149 1149
Lines 452798 454206 +1408
Branches 452798 454206 +1408
==========================================
+ Hits 375082 376079 +997
- Misses 54893 55267 +374
- Partials 22823 22860 +37 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
quwin
left a comment
There was a problem hiding this comment.
I reproduced a planning slowdown when the inputs are ordered on all but the final join key, with non-nullable keys and prefer_hash_join=false.
Fastest elapsed time over three (3) runs on my machine:
| Join keys | main (0d79252f6) |
This PR (a9d122f0e) |
|---|---|---|
| 4 | 0.025 s | 0.032 s |
| 8 | 0.026 s | 0.220 s |
| 12 | 0.034 s | 5.658 s |
| 14 | 0.039 s | 28.377 s |
Could we add a physical-planning benchmark to sql_planner for this case?
It can probably be tested better with run benchmark sql_planner, but I'm not whitelisted to call it.
Which issue does this PR close?
Rationale for this change
With
prefer_hash_join = false, the planner addsSortExecs underSortMergeJoinExeceven when both inputs are already sorted on the join keys:SortOptions::default()(ASC NULLS FIRST) for every key. Inputs sortedASC NULLS LAST,DESC, or with different options per key don't satisfy this, soEnforceSortingsorts both sides again.target_partitions > 1 && repartition_joins. Withtarget_partitions = 1,prefer_hash_join = falseis silently ignored and aHashJoinExecis planned. With a single partition no repartitioning happens, sorepartition_joinshas no effect there.What changes are included in this PR?
Both changes are in
physical_planner.rs.EquivalenceProperties::ordering_satisfy):SortOptions::default(), so existing plans are unchanged.target_partitions() > 1 && repartition_joins() && ...to(target_partitions() == 1 || repartition_joins()) && .... Fortarget_partitions > 1, behavior is unchanged.What is the testing strategy for this PR?
New SLT's :
sort_merge_join_sort_options.sltcovers inputs orderedASC NULLS LAST,DESC, and mixed options per key, checking that no extraSortExecis added.sort_merge_join.sltfortarget_partitions = 1.Are there any user-facing changes?
No