Yang Jie created SPARK-59122:
--------------------------------

             Summary: UnionExec fused into whole-stage codegen can fail with 
"key not found: numOutputRows"
                 Key: SPARK-59122
                 URL: https://issues.apache.org/jira/browse/SPARK-59122
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 5.0.0
            Reporter: Yang Jie


A {{UnionExec}} that whole-stage codegen fused can fail at execution with:

{code}
java.util.NoSuchElementException: key not found: numOutputRows
  at org.apache.spark.sql.execution.SparkPlan.longMetric(SparkPlan.scala:154)
  at 
org.apache.spark.sql.execution.CodegenSupport.metricTerm(WholeStageCodegenExec.scala:71)
  at 
org.apache.spark.sql.execution.UnionExec.doProduce(basicPhysicalOperators.scala:1148)
  at 
org.apache.spark.sql.execution.WholeStageCodegenExec.doCodeGen(WholeStageCodegenExec.scala:676)
{code}

Reproduced on master with default configuration:

{code}
spark.range(0, 200, 1, 4).selectExpr("id % 10 AS k", "id AS v")
  .groupBy("k").agg(sum("v").as("s")).createOrReplaceTempView("v")
spark.catalog.cacheTable("v")
spark.sql("SELECT k, abs(s) AS s FROM v UNION ALL SELECT k, s FROM v").collect()
{code}

{{UnionExec}} derives three things from its children's {{outputPartitioning}}: 
whether codegen
fusion applies, whether {{numOutputRows}} is registered, and which RDD 
{{doExecute}} builds. That
input is not stable while the plan is prepared -- {{InMemoryTableScanExec}} 
reads
{{cachedPlan.outputPartitioning}}, and the inner {{AdaptiveSparkPlanExec}} 
answers
{{UnknownPartitioning}} until its final plan exists.

In the query above an expression on one branch only keeps a {{ProjectExec}} 
from being collapsed
away, so the two children are shaped differently, {{comparePartitioning}} 
rejects the pair, and the
union looks plain and is fused. {{CollapseCodegenStages.insertInputAdapter}} 
then rebuilds the node
through {{withNewChildren}} and puts that copy inside the 
{{WholeStageCodegenExec}} it just built,
so the copy evaluates the gate for the first time after the cache stages have 
finalised. By then
both children report the same {{HashPartitioning}}, the gate answers 
"partitioning-aware",
{{metrics}} comes back empty, and the generated code still increments the 
metric.

Registering the metric unconditionally would turn the crash into a wrong 
answer: a fused union
concatenates its children's partitions, so if it went on claiming their 
{{HashPartitioning}} a
parent could satisfy a clustered distribution from an RDD that does not have it.

Affects the union codegen fusion added in SPARK-56482, which is present in 
4.2.0, branch-4.3 and
master.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to