-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Enforce co-partitioning for sort merge and symmetric hash joins #23480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,8 @@ use crate::projection::{ | |
| use crate::stream::EmptyRecordBatchStream; | ||
| use crate::{ | ||
| DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, ExecutionPlanProperties, | ||
| PlanProperties, RecordBatchStream, SendableRecordBatchStream, | ||
| InputDistributionRequirements, PlanProperties, RecordBatchStream, | ||
| SendableRecordBatchStream, | ||
| joins::StreamJoinPartitionMode, | ||
| metrics::{ExecutionPlanMetricsSet, MetricsSet}, | ||
| }; | ||
|
|
@@ -415,23 +416,26 @@ impl ExecutionPlan for SymmetricHashJoinExec { | |
| self.input_distribution_requirements().into_per_child() | ||
| } | ||
|
|
||
| fn input_distribution_requirements(&self) -> crate::InputDistributionRequirements { | ||
| crate::InputDistributionRequirements::new(match self.mode { | ||
| fn input_distribution_requirements(&self) -> InputDistributionRequirements { | ||
| match self.mode { | ||
| StreamJoinPartitionMode::Partitioned => { | ||
| let (left_expr, right_expr) = self | ||
| .on | ||
| .iter() | ||
| .map(|(l, r)| (Arc::clone(l) as _, Arc::clone(r) as _)) | ||
| .unzip(); | ||
| vec![ | ||
| InputDistributionRequirements::co_partitioned(vec![ | ||
| Distribution::KeyPartitioned(left_expr), | ||
| Distribution::KeyPartitioned(right_expr), | ||
| ] | ||
| ]) | ||
|
Comment on lines
+427
to
+430
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is it worth TODOs next to both of the uses of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's also my feeling after reviewing this PR, it seems to be doing close to nothing. Rather than changing 2 LOC in |
||
| } | ||
| StreamJoinPartitionMode::SinglePartition => { | ||
| vec![Distribution::SinglePartition, Distribution::SinglePartition] | ||
| InputDistributionRequirements::new(vec![ | ||
| Distribution::SinglePartition, | ||
| Distribution::SinglePartition, | ||
| ]) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -634,6 +634,80 @@ ORDER BY l.range_key; | |
| 30 600 | ||
| 35 700 | ||
|
|
||
| ########## | ||
| # TEST 18: Sort Merge Join Repartitions Compatible Range Inputs | ||
| # SortMergeJoinExec does not opt into Range satisfying KeyPartitioned. These | ||
| # compatible Range inputs receive Hash repartitions, a future | ||
| # Range satisfaction implementation should remove them. | ||
| ########## | ||
|
|
||
| statement ok | ||
| set datafusion.optimizer.prefer_hash_join = false; | ||
|
|
||
| query TT | ||
| EXPLAIN SELECT l.range_key, l.value, r.value | ||
| FROM range_partitioned l | ||
| JOIN range_partitioned r ON l.range_key = r.range_key; | ||
| ---- | ||
| physical_plan | ||
| 01)ProjectionExec: expr=[range_key@0 as range_key, value@1 as value, value@3 as value] | ||
| 02)--SortMergeJoinExec: join_type=Inner, on=[(range_key@0, range_key@0)] | ||
| 03)----SortExec: expr=[range_key@0 ASC], preserve_partitioning=[true] | ||
| 04)------RepartitionExec: partitioning=Hash([range_key@0], 4), input_partitions=4 | ||
| 05)--------DataSourceExec: file_groups=<slt:ignore>, projection=[range_key, value], output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=csv, has_header=false | ||
| 06)----SortExec: expr=[range_key@0 ASC], preserve_partitioning=[true] | ||
| 07)------RepartitionExec: partitioning=Hash([range_key@0], 4), input_partitions=4 | ||
| 08)--------DataSourceExec: file_groups=<slt:ignore>, projection=[range_key, value], output_partitioning=Range([range_key@0 ASC], [(10), (20), (30)], 4), file_type=csv, has_header=false | ||
|
Comment on lines
+655
to
+660
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remind me how is it that, even if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is because I did not opt into the range satisfaction since it isn't general yet with: I didn't do this just because I wanted to do the other operators first but with others picking those up I guess there is no reason fro me not to 👍 |
||
|
|
||
| query III | ||
| SELECT l.range_key, l.value, r.value | ||
| FROM range_partitioned l | ||
| JOIN range_partitioned r ON l.range_key = r.range_key | ||
| ORDER BY l.range_key; | ||
| ---- | ||
| 1 10 10 | ||
| 5 50 50 | ||
| 10 100 100 | ||
| 15 150 150 | ||
| 20 200 200 | ||
| 25 250 250 | ||
| 30 300 300 | ||
| 35 350 350 | ||
|
|
||
| statement ok | ||
| reset datafusion.optimizer.prefer_hash_join; | ||
|
|
||
| ########## | ||
| # TEST 19: Symmetric Hash Join Repartitions Inputs | ||
| # SymmetricHashJoinExec does not opt into Range satisfying KeyPartitioned. | ||
| # The unbounded stream fixture does not yet expose Range partitioning, | ||
| # so the join still receives Hash repartitions. | ||
| ########## | ||
|
|
||
| statement ok | ||
| set datafusion.optimizer.prefer_hash_join = true; | ||
|
|
||
| query TT | ||
| EXPLAIN SELECT l.range_key, l.value, r.value | ||
| FROM unbounded_range_like l | ||
| FULL JOIN unbounded_range_like r ON l.range_key = r.range_key; | ||
| ---- | ||
| physical_plan | ||
| 01)ProjectionExec: expr=[range_key@0 as range_key, value@1 as value, value@3 as value] | ||
| 02)--SymmetricHashJoinExec: mode=Partitioned, join_type=Full, on=[(range_key@0, range_key@0)] | ||
| 03)----RepartitionExec: partitioning=Hash([range_key@0], 4), input_partitions=1, maintains_sort_order=true | ||
| 04)------StreamingTableExec: partition_sizes=1, projection=[range_key, value], infinite_source=true, output_ordering=[range_key@0 ASC NULLS LAST] | ||
| 05)----RepartitionExec: partitioning=Hash([range_key@0], 4), input_partitions=1, maintains_sort_order=true | ||
| 06)------StreamingTableExec: partition_sizes=1, projection=[range_key, value], infinite_source=true, output_ordering=[range_key@0 ASC NULLS LAST] | ||
|
|
||
| query III rowsort | ||
| SELECT l.range_key, l.value, r.value | ||
| FROM unbounded_range_like l | ||
| FULL JOIN unbounded_range_like r ON l.range_key = r.range_key; | ||
| ---- | ||
| 1 10 10 | ||
| 5 50 50 | ||
|
|
||
| statement ok | ||
| reset datafusion.optimizer.prefer_hash_join; | ||
|
|
||
|
|
@@ -644,7 +718,7 @@ statement ok | |
| reset datafusion.optimizer.preserve_file_partitions; | ||
|
|
||
| ########## | ||
| # TEST 18: Union of Range Partitioned Inputs | ||
| # TEST 20: Union of Range Partitioned Inputs | ||
| # Each input exposes Range partitioning on range_key. These changes do not add a | ||
| # cross-child Range relationship for UNION ALL. | ||
| ########## | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using hash to prove that co-partitioning dows work fro an accepted partitioning type