ulysses-you commented on code in PR #59630:
URL: https://github.com/apache/spark/pull/59630#discussion_r4422047096


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/CacheManager.scala:
##########
@@ -707,8 +708,11 @@ class CacheManager extends Logging with 
AdaptiveSparkPlanHelper {
       // extra shuffle
       disableConfigs =
         disableConfigs :+ 
SQLConf.ADAPTIVE_EXECUTION_APPLY_FINAL_STAGE_SHUFFLE_OPTIMIZATIONS
+      SparkSession.getOrCloneSessionWithConfigsOff(session, disableConfigs,
+        Seq(SQLConf.PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED))

Review Comment:
   Updated the canChangeCachedPlanOutputPartitioning documentation in 
0a98c1e6a70. It now names union shuffle push-down and clarifies that false 
disables both optimizations for cache materialization, while true retains their 
configured enablement.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/CacheManager.scala:
##########
@@ -692,7 +692,8 @@ class CacheManager extends Logging with 
AdaptiveSparkPlanHelper {
 
   /**
    * If `CAN_CHANGE_CACHED_PLAN_OUTPUT_PARTITIONING` is enabled, return the 
session with disabled
-   * `ADAPTIVE_EXECUTION_APPLY_FINAL_STAGE_SHUFFLE_OPTIMIZATIONS`.
+   * `ADAPTIVE_EXECUTION_APPLY_FINAL_STAGE_SHUFFLE_OPTIMIZATIONS` and
+   * `PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED`.

Review Comment:
   Corrected "is enabled" to "is disabled" in 0a98c1e6a70, matching the 
condition below. The inversion predates this PR; the correction also covers the 
newly listed push-down setting.



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/adaptive/PushDownShuffleExchangeThroughUnionSuite.scala:
##########
@@ -0,0 +1,2496 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution.adaptive
+
+import org.apache.spark.rdd.RDD
+import org.apache.spark.sql.{DataFrame, QueryTest, Row}
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.expressions.{
+  Alias, Ascending, Attribute, AttributeReference, ExprId, GreaterThan, 
IsNotNull, Literal, Rand,
+  RowNumber, SortOrder}
+import org.apache.spark.sql.catalyst.optimizer.BuildLeft
+import org.apache.spark.sql.catalyst.plans.physical.{
+  ClusteredDistribution, CoalescedBoundary, CoalescedHashPartitioning, 
Distribution,
+  HashPartitioning, NullAwareHashPartitioning, Partitioning, 
PartitioningCollection,
+  RoundRobinPartitioning,
+  UnknownPartitioning}
+import org.apache.spark.sql.execution.{
+  FilterExec, InputAdapter, LocalLimitExec, ProjectExec, QueryExecution, 
ScalarSubquery, SortExec,
+  SparkPlan, StampUnionDecisions, SubqueryExec, UnaryExecNode, 
UnionConfSnapshot, UnionExec,
+  WholeStageCodegenExec}
+import org.apache.spark.sql.execution.exchange.{
+  ENSURE_REQUIREMENTS, EnsureRequirements, REPARTITION_BY_COL, 
REPARTITION_BY_NUM,
+  ShuffleExchangeExec, ValidateRequirements}
+import org.apache.spark.sql.execution.joins.{BroadcastHashJoinExec, 
ShuffledHashJoinExec, SortMergeJoinExec}
+import org.apache.spark.sql.execution.window.{Final => FinalWindowGroupLimit, 
Partial => PartialWindowGroupLimit, WindowGroupLimitExec}
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.test.SharedSparkSession
+import org.apache.spark.sql.types.IntegerType
+
+class PushDownShuffleExchangeThroughUnionSuite extends QueryTest with 
SharedSparkSession {
+  import testImplicits._
+
+  // The suite exercises the rule itself, so it is on for every test whatever 
the default is.
+  override protected def sparkConf =
+    super.sparkConf
+      .set(SQLConf.PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED.key, "true")
+      
.set(SQLConf.PUSH_DOWN_SHUFFLE_THROUGH_UNION_ALLOW_JOIN_INPUTS_ENABLED.key, 
"true")
+      
.set(SQLConf.PUSH_DOWN_SHUFFLE_THROUGH_UNION_KEEP_JOIN_BRANCHES_ENABLED.key, 
"true")
+      
.set(SQLConf.PUSH_DOWN_SHUFFLE_THROUGH_UNION_ALLOW_WITH_SUBQUERIES_ENABLED.key, 
"true")

Review Comment:
   Added a subquery-free arm under allowWithSubqueries=false in 0a98c1e6a70. It 
checks that the context flag remains false and the union exchange is removed. 
The scalar-subquery arm still checks the shared context and retained exchange. 
Unconditionally marking queries as containing subqueries makes the new arm fail.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala:
##########
@@ -8526,6 +8526,134 @@ object SQLConf {
       .booleanConf
       .createWithDefault(true)
 
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED =
+    buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.enabled")
+      .internal()
+      .doc("When set to true, during AQE query stage preparation a 
ShuffleExchangeExec with " +
+        "hash partitioning added by EnsureRequirements on top of a UnionExec 
is pushed down " +
+        "below the union, shuffling only the children that do not already 
report the picked " +
+        "layout. The target count must be at least the smaller of the replaced 
exchange's " +
+        "count and the effective initial shuffle partition count, including " +
+        "spark.sql.adaptive.coalescePartitions.initialPartitionNum when set. " 
+
+        "Removing the union's direct shuffle stage " +
+        "can reduce AQE skew handling and join conversion opportunities. 
Keeping a shuffled " +
+        "join child's layout can make skew join optimization or local shuffle 
reads invalidate " +
+        "an ancestor's distribution. AQE may discard that optimization or add 
another shuffle " +
+        "when skew optimization is forced. Join inputs and join branches are 
opt-in. Requires " +
+        "spark.sql.unionOutputPartitioning to be enabled. Adaptive cost 
compensation applies " +
+        "only to SimpleCostEvaluator. If 
spark.sql.adaptive.customCostEvaluatorClass is set, " +
+        "this rule is disabled unless explicitly enabled. With a custom 
evaluator, adapt its " +
+        "cost model to the replacement of one union exchange by multiple 
branch " +
+        "exchanges; otherwise the increased exchange count may cause AQE to 
reject a re-plan " +
+        "and its other runtime optimizations.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createOptional
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_LOOK_THROUGH_OPERATORS_ENABLED =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.lookThroughOperators.enabled")
+      .internal()
+      .doc("When set to true, the shuffle push-down through a union may look 
through the " +
+        "operators between the shuffle and the union: a project, a local sort, 
a filter, a " +
+        "partial aggregate and a partial window group limit. A filter, a 
partial aggregate and " +
+        "a partial window group limit reduce the data on their way, so the 
child is then " +
+        "shuffled before the reduction, which can send more data than the 
single shuffle it " +
+        "replaces. Requires " +
+        s"${PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED.key} to be enabled.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_KEEP_NON_COALESCIBLE_BRANCHES_ENABLED =
+    buildConf(
+      
"spark.sql.optimizer.pushDownShuffleThroughUnion.keepNonCoalescibleBranches.enabled")
+      .internal()
+      .doc("When set to true, fixed-count shuffles, bucketed or cached scans, 
and branches " +
+        "containing exploding joins may offer push-down targets. This can 
prevent coalescing " +
+        "of the consumer's compatible shuffle group or reduce its target 
partition size. " +
+        "Rebalance shuffles remain ineligible. This only affects target " +
+        "selection when spark.sql.adaptive.coalescePartitions.enabled is on. 
Requires " +
+        s"${PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED.key} to be enabled.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_MAX_MOVED_BRANCHES =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.maxMovedBranches")
+      .internal()
+      .doc("The maximum number of union children to move onto a push-down 
target. Existing " +
+        "unshared requirement or column-repartition exchanges may be 
retargeted; other moved " +
+        "children get a new exchange. Child count and order are preserved. 
Targets requiring " +
+        "more moved children are skipped. Zero only allows targets " +
+        "already offered by every child. Requires " +
+        s"${PUSH_DOWN_SHUFFLE_THROUGH_UNION_ENABLED.key} to be enabled.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .intConf
+      .checkValue(_ >= 0, "The maximum number of moved branches must be 
non-negative.")
+      .createWithDefault(3)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_MAX_UNION_CHILDREN =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.maxUnionChildren")
+      .internal()
+      .doc("The maximum total number of union children eligible for shuffle 
push-down, " +
+        "including kept and moved children. Zero disables push-down. This 
bounds the number " +
+        "of branch tails executed sequentially within each union output task.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .intConf
+      .checkValue(_ >= 0, "The maximum number of union children must be 
non-negative.")
+      .createWithDefault(10)
+
+  val 
PUSH_DOWN_SHUFFLE_THROUGH_UNION_ALLOW_PARTITION_KEYS_SUBSET_OF_CLUSTER_KEYS_ENABLED
 =
+    buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion." +
+      "allowPartitionKeysSubsetOfClusterKeys.enabled")
+      .internal()
+      .doc("When true, shuffle push-down may use partition keys that are a 
subset of the " +
+        "consumer's clustering keys if 
spark.sql.requireAllClusterKeysForCoPartition is false " +
+        "and the consumer accepts that distribution. Such targets can 
concentrate rows in " +
+        "fewer partitions. When false, targets must match all clustering 
keys.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_ALLOW_WITH_SUBQUERIES_ENABLED =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.allowWithSubqueries.enabled")
+      .internal()
+      .doc("When true, allows shuffle push-down in queries containing scalar, 
IN or dynamic " +
+        "pruning subqueries. Independent adaptive plans can make different 
rewrite decisions, " +
+        "reducing exchange reuse or disabling broadcast-only dynamic partition 
pruning. " +
+        "When false, push-down is disabled for the main query and all its 
subqueries.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_ALLOW_JOIN_INPUTS_ENABLED =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.allowJoinInputs.enabled")
+      .internal()
+      .doc("When true, allows shuffle push-down for inputs of shuffled joins. 
Removing a " +
+        "direct shuffle stage can prevent adaptive skew join optimization or 
join conversion.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(false)
+
+  val PUSH_DOWN_SHUFFLE_THROUGH_UNION_KEEP_JOIN_BRANCHES_ENABLED =
+    
buildConf("spark.sql.optimizer.pushDownShuffleThroughUnion.keepJoinBranches.enabled")
+      .internal()
+      .doc("When true, union children may offer layouts produced by shuffled 
joins above their " +

Review Comment:
   Updated the configuration documentation in 0a98c1e6a70 to include 
broadcast-hash-join probes backed by requirement shuffles, including stages and 
adaptive reads. User column-repartition-backed probes retain their existing 
eligibility.



-- 
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