-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-58558][SQL] Make requireAllClusterKeysForCoPartition check key coverage instead of exact match for SPJ #57762
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: master
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 |
|---|---|---|
|
|
@@ -35,7 +35,6 @@ import org.apache.spark.sql.execution.joins.{ShuffledHashJoinExec, SortMergeJoin | |
| import org.apache.spark.sql.execution.python.FlatMapCoGroupsInPandasExec | ||
| import org.apache.spark.sql.execution.window.WindowExec | ||
| import org.apache.spark.sql.internal.SQLConf | ||
| import org.apache.spark.sql.internal.SQLConf.REQUIRE_ALL_CLUSTER_KEYS_FOR_CO_PARTITION | ||
| import org.apache.spark.sql.test.SharedSparkSession | ||
| import org.apache.spark.sql.types.{IntegerType, StructField, StructType} | ||
|
|
||
|
|
@@ -862,38 +861,24 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| assert(right.expressions === Seq(bucket(4, exprA), years(exprC))) | ||
| case other => fail(other.toString) | ||
| } | ||
|
|
||
| // by default spark.sql.requireAllClusterKeysForCoPartition is true, so when there isn't | ||
| // exact match on all partition keys, Spark will fallback to shuffle. | ||
| plan1 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = KeyedPartitioning(bucket(4, exprA) :: bucket(4, exprB) :: Nil, Seq.empty) | ||
| ) | ||
| plan2 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = KeyedPartitioning(bucket(4, exprA) :: bucket(4, exprC) :: Nil, Seq.empty) | ||
| ) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, ShuffleExchangeExec(left: HashPartitioning, _, _, _), _), | ||
| SortExec(_, _, ShuffleExchangeExec(right: HashPartitioning, _, _, _), _), _) => | ||
| assert(left.expressions === Seq(exprA, exprB, exprB)) | ||
| assert(right.expressions === Seq(exprA, exprC, exprC)) | ||
| case other => fail(other.toString) | ||
| } | ||
| } | ||
|
|
||
| test(s"KeyedPartitioning with ${REQUIRE_ALL_CLUSTER_KEYS_FOR_CO_PARTITION.key} = false") { | ||
| test("KeyedPartitioning with subset of join keys") { | ||
| var plan1 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = KeyedPartitioning(bucket(4, exprB) :: years(exprC) :: Nil, Seq.empty) | ||
| ) | ||
| var plan2 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = KeyedPartitioning(bucket(4, exprC) :: years(exprB) :: Nil, Seq.empty) | ||
| ) | ||
|
|
||
| // simple case | ||
| // simple case: join key exprA is not covered by either side's partition keys, so by default | ||
| // the coverage check of requireAllClusterKeysForCoPartition falls back to shuffle to avoid | ||
| // joining on a partitioning coarser than the join keys | ||
| var smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprC :: Nil, exprA :: exprC :: exprB :: Nil, Inner, None, plan1, plan2) | ||
| assert(EnsureRequirements.apply(smjExec) | ||
| .collect { case s: ShuffleExchangeLike => s }.length == 2) | ||
| // with requireAllClusterKeysForCoPartition=false, SPJ is allowed | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, DummySparkPlan(_, _, left: KeyedPartitioning, _, _), _), | ||
|
|
@@ -912,7 +897,7 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| ) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, DummySparkPlan(_, _, left: KeyedPartitioning, _, _), _), | ||
| SortExec(_, _, DummySparkPlan(_, _, right: KeyedPartitioning, _, _), _), _) => | ||
|
|
@@ -930,7 +915,7 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| KeyedPartitioning(years(exprA) :: bucket(4, exprC) :: days(exprA) :: Nil, Seq.empty)) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, DummySparkPlan(_, _, left: KeyedPartitioning, _, _), _), | ||
| SortExec(_, _, DummySparkPlan(_, _, right: KeyedPartitioning, _, _), _), _) => | ||
|
|
@@ -967,7 +952,7 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| ) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, ShuffleExchangeExec(left: HashPartitioning, _, _, _), _), | ||
| SortExec(_, _, ShuffleExchangeExec(right: HashPartitioning, _, _, _), _), _) => | ||
|
|
@@ -985,7 +970,7 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| ) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, ShuffleExchangeExec(left: HashPartitioning, _, _, _), _), | ||
| SortExec(_, _, ShuffleExchangeExec(right: HashPartitioning, _, _, _), _), _) => | ||
|
|
@@ -1006,7 +991,7 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| ) | ||
| smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
| applyEnsureRequirementsWithSubsetKeys(smjExec) match { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, ShuffleExchangeExec(left: HashPartitioning, _, _, _), _), | ||
| SortExec(_, _, ShuffleExchangeExec(right: HashPartitioning, _, _, _), _), _) => | ||
|
|
@@ -1016,6 +1001,32 @@ class EnsureRequirementsSuite extends SharedSparkSession { | |
| } | ||
| } | ||
|
|
||
| test("KeyedPartitioning: duplicated join keys do not block SPJ") { | ||
| // The coverage check of requireAllClusterKeysForCoPartition ignores key order and | ||
| // duplicated cluster keys: join keys [a, b, b] are fully covered by partition keys | ||
| // on [a, b], so SPJ is allowed with either config value. | ||
| val plan1 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = | ||
| KeyedPartitioning(bucket(4, exprA) :: bucket(4, exprB) :: Nil, Seq.empty)) | ||
| val plan2 = new DummySparkPlanWithBatchScanChild( | ||
| outputPartitioning = | ||
| KeyedPartitioning(bucket(4, exprA) :: bucket(4, exprC) :: Nil, Seq.empty)) | ||
| val smjExec = SortMergeJoinExec( | ||
| exprA :: exprB :: exprB :: Nil, exprA :: exprC :: exprC :: Nil, Inner, None, plan1, plan2) | ||
|
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. Finding 8. This key list can't come from a query: The But all 8 also need smjExec = SortMergeJoinExec(
exprA :: exprB :: Nil, exprA :: exprC :: Nil, Inner, None, plan1, plan2)attrs |
||
| Seq("true", "false").foreach { requireAllKeys => | ||
| withSQLConf(SQLConf.REQUIRE_ALL_CLUSTER_KEYS_FOR_CO_PARTITION.key -> requireAllKeys) { | ||
| EnsureRequirements.apply(smjExec) match { | ||
| case SortMergeJoinExec(_, _, _, _, | ||
| SortExec(_, _, DummySparkPlan(_, _, left: KeyedPartitioning, _, _), _), | ||
| SortExec(_, _, DummySparkPlan(_, _, right: KeyedPartitioning, _, _), _), _) => | ||
| assert(left.expressions === Seq(bucket(4, exprA), bucket(4, exprB))) | ||
| assert(right.expressions === Seq(bucket(4, exprA), bucket(4, exprC))) | ||
| case other => fail(s"Expected no shuffle, but got: $other") | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("SPARK-41413: check compatibility when partition values mismatch") { | ||
| withSQLConf(SQLConf.V2_BUCKETING_PUSH_PART_VALUES_ENABLED.key -> "true") { | ||
| val leftPartValues = Seq(Array[Any](1, 1), Array[Any](2, 2)).map(new GenericInternalRow(_)) | ||
|
|
||
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.
a small doc fix, the config
spark.sql.requireAllClusterKeysForCoPartitionis introduced in 3.3.0