[SPARK-59025][SQL] Fix subset join key grouping when the keyed side reports a PartitioningCollection - #58315
[SPARK-59025][SQL] Fix subset join key grouping when the keyed side reports a PartitioningCollection#58315dongjoon-hyun wants to merge 2 commits into
Conversation
…eports a PartitioningCollection
b71764d to
e2117ce
Compare
|
CI is running here. |
|
cc @peter-toth , too |
|
This is independent from other SPJ fixes, I'm reviewing it now. |
peter-toth
left a comment
There was a problem hiding this comment.
Thanks for the PR, @dongjoon-hyun!
I traced the failure independently and landed on the same diagnosis. With allowKeysSubsetOfPartitionKeys on, a keyed child whose outputPartitioning is a PartitioningCollection reports a ShuffleSpecCollection, so case Some(KeyedShuffleSpec(...)) never fires - the keyed side keeps its 4 partitions while the other side is re-shuffled into the 3-partition projected layout, and the join's PartitioningCollection then rejects the pair. Unwrapping to the head spec is the right minimal shape, since that is the spec ShuffleSpecCollection.numPartitions and createPartitioning already delegate to. I also checked the new test is effective against the current base rather than the PR's: with SPARK-58988's partitioning.scala applied on top of 14e21716 (the only commit touching these files since), it still fails with the reported require, and it passes at e2117ce. KeyGroupedPartitioningSuite + EnsureRequirementsSuite + ProjectedOrderingAndPartitioningSuite + GroupPartitionsExecSuite are 175/175 green at head. Nothing blocking from me.
Non-blocking
- 1. JIRA affects-version: SPARK-59025 says 5.0.0, but
EnsureRequirements.scalais byte-identical between this PR's base andapache/branch-4.x, so 4.4.0 is affected too. The same unguarded match also exists back tobranch-4.0under the olderKeyGroupedShuffleSpecname, which may be worth a look before you pick fix-versions.
Minor
- 2. Helper placement: the new helper lands between
hasKeyedPartitioningandtryEnableSortedMerge, which is the only thing that uses it. - 3. Test assertions: the test comment states the keyed side must drop from 4 partitions to 3, but nothing asserts it.
- 4. Description: "How was this patch tested?" is just "Pass the CIs.", so the added regression test goes unmentioned in the commit message that
dev/merge_spark_pr.pybuilds. And in the user-facing-change section, "No behavior change." contradicts the sentence right after it - queries that used to throw now return rows. Suggest naming the test in the first, and dropping "No behavior change." from the second.
| // Unwraps a `ShuffleSpecCollection` (possibly nested) to the spec that its | ||
| // `createPartitioning` delegates to, i.e. the head spec. | ||
| @tailrec | ||
| private def unwrapSpecCollection(spec: ShuffleSpec): ShuffleSpec = spec match { |
There was a problem hiding this comment.
Finding 2. hasKeyedPartitioning above (:307-310) exists only to serve tryEnableSortedMerge's pruning predicate at :339, and the new helper is inserted between the two, splitting that pair.
It reads better next to the other shuffle-spec helpers - withJoinKeyPositions (:778) and createKeyedShuffleSpec (:793) - or directly after ensureDistributionAndOrdering, its only caller.
| assert(shuffles.size == 1, "only the non-keyed side should be shuffled") | ||
| val groupPartitions = collectGroupPartitions(df.queryExecution.executedPlan) | ||
| assert(groupPartitions.size == 1 && groupPartitions.head.joinKeyPositions.isDefined, | ||
| "the keyed side should be grouped by the join keys") |
There was a problem hiding this comment.
Finding 3. joinKeyPositions.isDefined would also hold if the positions covered both partition columns, in which case the keyed side would stay at 4 partitions and the fix's point would be lost. The comment at :2580-2581 states the number, so it is worth pinning:
| "the keyed side should be grouped by the join keys") | |
| "the keyed side should be grouped by the join keys") | |
| assert(groupPartitions.head.outputPartitioning.numPartitions == 3, | |
| "the keyed side should be grouped down to 3 partitions") | |
| assert(shuffles.head.outputPartitioning.numPartitions == 3, | |
| "the shuffled side should match the 3 grouped partitions") |
Both assertions pass at e2117ce - I ran them.
|
Thank you, @peter-toth . I addressed your comments. For old release branches, I'll try to backport and update the JIRA info. |
peter-toth
left a comment
There was a problem hiding this comment.
Re-checked through 103dad17 - findings 1-4 resolved (affects-version now 4.4.0, unwrapSpecCollection moved next to withJoinKeyPositions, both partition-count assertions in, description corrected), nothing new. Re-measured at the new head rather than carrying the old verdict: with SPARK-58988's partitioning.scala on top of the base, the test still fails with the same require without the EnsureRequirements change, and KeyGroupedPartitioningSuite + EnsureRequirementsSuite + ProjectedOrderingAndPartitioningSuite + GroupPartitionsExecSuite are 175/175 green.
Thanks for working through all of these, @dongjoon-hyun - nothing left open from my side.
|
Could you approve this PR, @peter-toth ? |
|
Thank you always, @peter-toth ! |
Sorry, I forgot to do it. 😄 Approved now. |
…eports a PartitioningCollection
### What changes were proposed in this pull request?
Fix the post-shuffle regrouping in `EnsureRequirements` for storage-partitioned joins with `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true`. The unwrap that pushes join key positions down to the keyed side only matches a bare `KeyedShuffleSpec`:
```scala
bestSpecOpt match {
case Some(KeyedShuffleSpec(_, _, Some(joinKeyPositions))) =>
withJoinKeyPositions(child, joinKeyPositions)
case _ => child
}
```
When the keyed child's `outputPartitioning` is a `PartitioningCollection` (e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases like `SELECT a AS a1, a AS a2, b`), its spec is a `ShuffleSpecCollection` wrapping the `KeyedShuffleSpec`, so the match falls through and no `GroupPartitionsExec` is inserted. This PR unwraps a (possibly nested) `ShuffleSpecCollection` to its head spec — the same spec `ShuffleSpecCollection.createPartitioning` delegates to — before the match.
### Why are the changes needed?
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
```
java.lang.IllegalArgumentException: requirement failed:
All KeyedPartitionings in a PartitioningCollection must have equal partitionKeys
```
### Does this PR introduce _any_ user-facing change?
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
### How was this patch tested?
Pass the CIs.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Fable 5
Closes #58315 from dongjoon-hyun/SPARK-59025.
Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 25bae64)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
…eports a PartitioningCollection
### What changes were proposed in this pull request?
Fix the post-shuffle regrouping in `EnsureRequirements` for storage-partitioned joins with `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true`. The unwrap that pushes join key positions down to the keyed side only matches a bare `KeyedShuffleSpec`:
```scala
bestSpecOpt match {
case Some(KeyedShuffleSpec(_, _, Some(joinKeyPositions))) =>
withJoinKeyPositions(child, joinKeyPositions)
case _ => child
}
```
When the keyed child's `outputPartitioning` is a `PartitioningCollection` (e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases like `SELECT a AS a1, a AS a2, b`), its spec is a `ShuffleSpecCollection` wrapping the `KeyedShuffleSpec`, so the match falls through and no `GroupPartitionsExec` is inserted. This PR unwraps a (possibly nested) `ShuffleSpecCollection` to its head spec — the same spec `ShuffleSpecCollection.createPartitioning` delegates to — before the match.
### Why are the changes needed?
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
```
java.lang.IllegalArgumentException: requirement failed:
All KeyedPartitionings in a PartitioningCollection must have equal partitionKeys
```
### Does this PR introduce _any_ user-facing change?
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
### How was this patch tested?
Pass the CIs.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Fable 5
Closes #58315 from dongjoon-hyun/SPARK-59025.
Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 25bae64)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
…eports a PartitioningCollection
### What changes were proposed in this pull request?
Fix the post-shuffle regrouping in `EnsureRequirements` for storage-partitioned joins with `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true`. The unwrap that pushes join key positions down to the keyed side only matches a bare `KeyedShuffleSpec`:
```scala
bestSpecOpt match {
case Some(KeyedShuffleSpec(_, _, Some(joinKeyPositions))) =>
withJoinKeyPositions(child, joinKeyPositions)
case _ => child
}
```
When the keyed child's `outputPartitioning` is a `PartitioningCollection` (e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases like `SELECT a AS a1, a AS a2, b`), its spec is a `ShuffleSpecCollection` wrapping the `KeyedShuffleSpec`, so the match falls through and no `GroupPartitionsExec` is inserted. This PR unwraps a (possibly nested) `ShuffleSpecCollection` to its head spec — the same spec `ShuffleSpecCollection.createPartitioning` delegates to — before the match.
### Why are the changes needed?
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
```
java.lang.IllegalArgumentException: requirement failed:
All KeyedPartitionings in a PartitioningCollection must have equal partitionKeys
```
### Does this PR introduce _any_ user-facing change?
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
### How was this patch tested?
Pass the CIs.
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Fable 5
Closes #58315 from dongjoon-hyun/SPARK-59025.
Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 25bae64)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 7cc9194)
…eports a PartitioningCollection
Fix the post-shuffle regrouping in `EnsureRequirements` for storage-partitioned joins with `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true`. The unwrap that pushes join key positions down to the keyed side only matches a bare `KeyedShuffleSpec`:
```scala
bestSpecOpt match {
case Some(KeyedShuffleSpec(_, _, Some(joinKeyPositions))) =>
withJoinKeyPositions(child, joinKeyPositions)
case _ => child
}
```
When the keyed child's `outputPartitioning` is a `PartitioningCollection` (e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases like `SELECT a AS a1, a AS a2, b`), its spec is a `ShuffleSpecCollection` wrapping the `KeyedShuffleSpec`, so the match falls through and no `GroupPartitionsExec` is inserted. This PR unwraps a (possibly nested) `ShuffleSpecCollection` to its head spec — the same spec `ShuffleSpecCollection.createPartitioning` delegates to — before the match.
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
```
java.lang.IllegalArgumentException: requirement failed:
All KeyedPartitionings in a PartitioningCollection must have equal partitionKeys
```
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
Pass the CIs.
Generated-by: Claude Fable 5
Closes #58315 from dongjoon-hyun/SPARK-59025.
Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 25bae64)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 7cc9194)
(cherry picked from commit 91cd9a3)
…eports a PartitioningCollection
Fix the post-shuffle regrouping in `EnsureRequirements` for storage-partitioned joins with `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true`. The unwrap that pushes join key positions down to the keyed side only matches a bare `KeyedShuffleSpec`:
```scala
bestSpecOpt match {
case Some(KeyedShuffleSpec(_, _, Some(joinKeyPositions))) =>
withJoinKeyPositions(child, joinKeyPositions)
case _ => child
}
```
When the keyed child's `outputPartitioning` is a `PartitioningCollection` (e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases like `SELECT a AS a1, a AS a2, b`), its spec is a `ShuffleSpecCollection` wrapping the `KeyedShuffleSpec`, so the match falls through and no `GroupPartitionsExec` is inserted. This PR unwraps a (possibly nested) `ShuffleSpecCollection` to its head spec — the same spec `ShuffleSpecCollection.createPartitioning` delegates to — before the match.
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
```
java.lang.IllegalArgumentException: requirement failed:
All KeyedPartitionings in a PartitioningCollection must have equal partitionKeys
```
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
Pass the CIs.
Generated-by: Claude Fable 5
Closes #58315 from dongjoon-hyun/SPARK-59025.
Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 25bae64)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
(cherry picked from commit 7cc9194)
(cherry picked from commit 91cd9a3)
(cherry picked from commit 48c19fa)
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
What changes were proposed in this pull request?
Fix the post-shuffle regrouping in
EnsureRequirementsfor storage-partitioned joins withspark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys=true. The unwrap that pushes join key positions down to the keyed side only matches a bareKeyedShuffleSpec:When the keyed child's
outputPartitioningis aPartitioningCollection(e.g. an inner SPJ join result, or a projection duplicating a partition column under multiple aliases likeSELECT a AS a1, a AS a2, b), its spec is aShuffleSpecCollectionwrapping theKeyedShuffleSpec, so the match falls through and noGroupPartitionsExecis inserted. This PR unwraps a (possibly nested)ShuffleSpecCollectionto its head spec — the same specShuffleSpecCollection.createPartitioningdelegates to — before the match.Why are the changes needed?
Without the fix, the keyed side keeps its N ungrouped partitions while the other side is shuffled into the projected layout with M partitions, and planning fails with:
Does this PR introduce any user-facing change?
The affected queries used to fail with the error above; they now run correctly, shuffling only the non-keyed side.
How was this patch tested?
Pass the CIs.
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Fable 5