@@ -614,8 +614,13 @@ case class EnsureRequirements(
614614 logInfo(log " Skipping partially clustered distribution as it cannot be applied for " +
615615 log " join type ' ${MDC (LogKeys .JOIN_TYPE , joinType)}' " )
616616 } else {
617- val unwrappedLeft = unwrapGroupPartitions(left)
618- val unwrappedRight = unwrapGroupPartitions(right)
617+ // The pre-alignment plan of each side and the grouping this rule inserted over it,
618+ // read once: the statistics and the original partition keys below come from the
619+ // plan, the positions projecting them from the grouping.
620+ val leftGrouping = innermostGroupPartition(left)
621+ val rightGrouping = innermostGroupPartition(right)
622+ val unwrappedLeft = leftGrouping.map(_._1.child).getOrElse(left)
623+ val unwrappedRight = rightGrouping.map(_._1.child).getOrElse(right)
619624
620625 val leftLink = unwrappedLeft.logicalLink
621626 val rightLink = unwrappedRight.logicalLink
@@ -637,11 +642,16 @@ case class EnsureRequirements(
637642 | """ .stripMargin)
638643 leftLink.get.stats.sizeInBytes < rightLink.get.stats.sizeInBytes
639644 } else {
640- // As a simple heuristic, we pick the side with fewer number of partitions
641- // to apply the grouping & replication of partitions
645+ // As a simple heuristic, we pick the side with fewer number of partitions to
646+ // apply the grouping & replication of partitions. The counts read the
647+ // pre-alignment plans, for the same reason the statistics do: on a re-run both
648+ // aligned reports hold the same number of keys, so comparing them decides nothing.
642649 logInfo(" Using number of partitions to determine which side of join " +
643650 " to fully cluster partition values" )
644- leftPartKeys.size < rightPartKeys.size
651+ PartitioningCollection .numKeyedPartitions(unwrappedLeft.outputPartitioning)
652+ .getOrElse(leftPartKeys.size) <
653+ PartitioningCollection .numKeyedPartitions(unwrappedRight.outputPartitioning)
654+ .getOrElse(rightPartKeys.size)
645655 }
646656
647657 replicateRightSide = ! replicateLeftSide
@@ -661,18 +671,19 @@ case class EnsureRequirements(
661671 replicateRightSide = false
662672 } else {
663673 // In partially clustered distribution, we should use un-grouped partition values.
664- // The positions projecting them come from the innermost grouping when there is
665- // one: like in `applyGroupPartitions`, they were computed against the raw
666- // partition keys, while the spec's were computed against the node's already
667- // projected report on a re-run.
674+ // The child and the positions projecting its keys come from the same grouping:
675+ // the keys from the node's child, the positions from the node itself, falling back
676+ // to the spec's when there is no grouping. Like in `applyGroupPartitions`, the
677+ // node's positions were computed against the raw partition keys, while the spec's
678+ // were computed against the node's already projected report on a re-run.
668679 val (partiallyClusteredChild, partiallyClusteredPositions) =
669680 if (replicateLeftSide) {
670681 (unwrappedRight,
671- innermostGroupPartition(right) .flatMap(_._1.joinKeyPositions)
682+ rightGrouping .flatMap(_._1.joinKeyPositions)
672683 .orElse(rightSpec.joinKeyPositions))
673684 } else {
674685 (unwrappedLeft,
675- innermostGroupPartition(left) .flatMap(_._1.joinKeyPositions)
686+ leftGrouping .flatMap(_._1.joinKeyPositions)
676687 .orElse(leftSpec.joinKeyPositions))
677688 }
678689 // The pre-alignment plan of the side that keeps its splits: its partitioning
@@ -801,12 +812,9 @@ case class EnsureRequirements(
801812 }
802813
803814 /**
804- * Unwraps the `GroupPartitionsExec` nodes this rule inserted over a join child, down to the
805- * pre-alignment plan, per the descent of [[innermostGroupPartition ]].
806- *
807- * The statistics-based replicate-side choice and the original partition keys below must read
808- * from the pre-alignment plan on every pass: the local sort one level down carries no
809- * `logicalLink` and reports the aligned layout instead.
815+ * Unwraps the groupings and local sorts this rule inserted over a child, down to the
816+ * pre-alignment plan, per the descent of [[innermostGroupPartition ]]. Peeling one level stops
817+ * at the local sort this rule added, leaving the earlier pass's alignment in place.
810818 */
811819 private def unwrapGroupPartitions (plan : SparkPlan ): SparkPlan =
812820 innermostGroupPartition(plan).map(_._1.child).getOrElse(plan)
@@ -827,6 +835,9 @@ case class EnsureRequirements(
827835 g.copy(
828836 joinKeyPositions = g.joinKeyPositions.orElse(joinKeyPositions),
829837 expectedPartitionKeys = Some (mergedPartitionKeys),
838+ // Unlike `joinKeyPositions`, these need no `orElse`. A re-run with reducers never reaches
839+ // here. Both sides then report the same reduced keys, so `isCompatible` above is true and
840+ // the whole block is skipped.
830841 reducers = reducers,
831842 distributePartitions = distributePartitions)
832843 }.getOrElse {
0 commit comments