XiDuo You created SPARK-61211:
---------------------------------

             Summary: Avoid incorrect union partitioning with duplicate child 
expression IDs
                 Key: SPARK-61211
                 URL: https://issues.apache.org/jira/browse/SPARK-61211
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 5.0.0
            Reporter: XiDuo You


UnionExec.prepareOutputPartitioning can report an incorrect hash layout when a 
child output repeats an exprId. Runtime BindReferences selects the first 
occurrence, while AttributeMap(child.output.zip(union.output)) retains the last 
occurrence. A union can consequently advertise clustering on a column whose 
rows were actually shuffled by another column, causing EnsureRequirements to 
omit a necessary exchange.

This is independent of SPARK-60379's shuffle push-down. Reviewers reproduced 
the existing bug on merge base 352ac9bf228 with push-down off, and with AQE 
both on and off. Disabling spark.sql.unionOutputPartitioning restores the 
expected result. The exact earliest affected release has not yet been 
determined.

Reported reproduction (Scala):
{code:scala}
import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
import spark.implicits._

spark.conf.set("spark.sql.analyzer.unionIsResolvedWhenDuplicatesPerChildResolved",
 "false")
spark.conf.set("spark.sql.enforceTypeCoercionBeforeUnionDeduplication.enabled", 
"false")
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "false")
spark.conf.set("spark.sql.shuffle.partitions", "5")
spark.conf.set("spark.sql.unionOutputPartitioning", "true")
// Test harness controls; avoid resolver dual-run validation and codegen 
compilation of duplicates.
spark.conf.set("spark.sql.analyzer.singlePassResolver.dualRunWithLegacy", 
"false")
spark.conf.set("spark.sql.codegen.wholeStage", "false")

val p1 = spark.range(1000).selectExpr("id p", "id w").select($"p", $"p", $"w")
val p2 = spark.range(1000, 11000).selectExpr("id q", "id % 1000 r", "id w2")
val x = spark.range(1000).selectExpr("id x", "id c", "id u").repartition($"c")
val r = p1.union(p2).repartition(p1.col("p"))
val result = x.union(r).withColumn("rn",
  row_number().over(Window.partitionBy(x.col("c")).orderBy(x.col("u"))))
result.collect().count(_.getInt(3) == 1)
{code}

Expected: 1000. Reported actual: 4582. Collect all columns before counting to 
retain the triggering plan shape.

The duplicate second child reports hash(p), physically bound to its first 
column. Its mapping into the outer union reports hash(c), corresponding to the 
second column. Both outer children then appear to report hash(c), although 
their rows are not co-located by c.

Investigate a conservative refusal to propagate partitioning from ambiguous 
child outputs, or a mapping aligned with first-occurrence runtime binding, with 
coverage for both mapping directions and all supported partitioning types. Keep 
this general UnionExec fix separate from the narrower push-down eligibility 
guards in SPARK-60379.

Reviewer evidence:
https://github.com/apache/spark/pull/59630#discussion_r4495483202
https://github.com/apache/spark/pull/59630#discussion_r4493424741




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

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

Reply via email to