LuciferYang commented on code in PR #58527:
URL: https://github.com/apache/spark/pull/58527#discussion_r3939094520


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/exchange/EnsureRequirements.scala:
##########
@@ -247,30 +246,50 @@ case class EnsureRequirements(
         }
       }
 
+      // A `ShuffleSpecCollection` answers `isCompatibleWith` if *any* of its 
members does, so the
+      // collection alone does not say which member the sides agreed on. The 
projection pushed into
+      // a compatible child and the partitioning built for a re-shuffled child 
both have to come
+      // from one member, otherwise the sides end up grouped on different 
keys, or on a key set the
+      // child does not even have. Pick that member once, preferring the 
finest when several
+      // qualify. Only the branch that shuffles a child reads these, hence 
`lazy`.
+      lazy val matchedIndexes = bestSpecOpt.toSeq.flatMap { best =>
+        childrenIndexes.filter(i => best.isCompatibleWith(specs(i)))
+      }
+      lazy val bestMemberOpt = bestSpecOpt.flatMap { best =>
+        val matchedMembers = matchedIndexes.map(i => flattenSpec(specs(i)))
+        // No member serving every matched child means there is no layout to 
align them on, so they
+        // all take the ordinary shuffle. That needs three or more clustered 
children, since with
+        // two the member that reported the match serves both, and no operator 
has three today.
+        flattenSpec(best)
+          .filter(m => matchedMembers.forall(_.exists(m.isCompatibleWith)))
+          .maxByOption(_.numPartitions)

Review Comment:
   `createKeyedShuffleSpec` still resolves a `PartitioningCollection` with 
`collectFirst`, taking the first satisfying member, while this PR makes the 
per-child branch resolve the finest member both sides agreed on. The default 
`requireAllClusterKeysForCoPartition=true` keeps partial-coverage members out 
of the candidates, so this only shows up with 
`requireAllClusterKeysForCoPartition=false` plus 
`allowKeysSubsetOfPartitionKeys=true`: when both sides report alias 
cross-products with a coarse member first, SPJ pairs the coarse members and 
joins on them, leaving the finer pairing unused; with coarse first on one side 
and fine first on the other, the picks fail to pair and the per-child branch 
takes over, grouping each side on its finest matching member, so what is 
skipped is the SPJ partition-value pushdown, not a shuffle. Results stay 
correct. If you align it, the choice cannot be an independent per-side finest 
(a side with only coarse members would then fail to pair); it has to pair
  like `bestMemberOpt` does, by finest member compatible with some member of 
the other side. Either fixing it here or a follow-up works for me.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/exchange/EnsureRequirements.scala:
##########
@@ -247,30 +246,50 @@ case class EnsureRequirements(
         }
       }
 
+      // A `ShuffleSpecCollection` answers `isCompatibleWith` if *any* of its 
members does, so the
+      // collection alone does not say which member the sides agreed on. The 
projection pushed into
+      // a compatible child and the partitioning built for a re-shuffled child 
both have to come
+      // from one member, otherwise the sides end up grouped on different 
keys, or on a key set the
+      // child does not even have. Pick that member once, preferring the 
finest when several
+      // qualify. Only the branch that shuffles a child reads these, hence 
`lazy`.
+      lazy val matchedIndexes = bestSpecOpt.toSeq.flatMap { best =>
+        childrenIndexes.filter(i => best.isCompatibleWith(specs(i)))
+      }
+      lazy val bestMemberOpt = bestSpecOpt.flatMap { best =>

Review Comment:
   `bestSpecOpt` is still picked with `ShuffleSpecCollection.numPartitions`, 
which comes from `specs.head`. A collection like [coarse head with 2 
partitions, fine member with 200] competes as 2 in the `maxBy`, loses to a 
sibling child's plain 100-partition spec, and the side holding the fine member 
gets re-shuffled onto 100 partitions when it could have stayed unshuffled as 
the best. The selection line predates this PR, and in this shape it behaves 
exactly as before (the losing side took the same full re-shuffle), so this is 
purely pre-existing. Not blocking; worth a follow-up comparing best candidates 
at member level at the call site (changing `numPartitions` itself would affect 
other readers).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to