[ 
https://issues.apache.org/jira/browse/SPARK-59252?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated SPARK-59252:
-----------------------------------
    Labels: pull-request-available  (was: )

> SPJ scan reports a different partitioning at execution than it did at planning
> ------------------------------------------------------------------------------
>
>                 Key: SPARK-59252
>                 URL: https://issues.apache.org/jira/browse/SPARK-59252
>             Project: Spark
>          Issue Type: Bug
>          Components: SQL
>    Affects Versions: 4.2.0, 4.3.0, 5.0.0, 4.4.0
>            Reporter: Peter Toth
>            Priority: Major
>              Labels: pull-request-available
>
> h2. Problem
> {{DataSourceV2ScanExecBase.outputPartitioning}} is a {{def}} that reads 
> {{conf.v2BucketingEnabled}} off the live session conf, so it can answer 
> differently at execution time than it did at planning time. When it does, the 
> plan is already committed to the answer it gave the planner.
> {code:scala}
> override def outputPartitioning: physical.Partitioning = {
>   keyGroupedPartitioning match {
>     case Some(exprs) if conf.v2BucketingEnabled && 
> KeyedPartitioning.supportsExpressions(exprs) &&
>         inputPartitions.nonEmpty && 
> inputPartitions.forall(_.isInstanceOf[HasPartitionKey]) =>
>       ...
>       KeyedPartitioning(exprs, partitionKeys)
>     case _ =>
>       super.outputPartitioning
>   }
> }
> {code}
> h2. Reproduction, measured
> Two tables bucketed the same way, joined on the bucket column. Force the 
> plan, then turn the config off, then run it:
> {code:scala}
> val df = sql("SELECT l.id FROM testcat.ns.l4 l JOIN testcat.ns.r4 r ON l.id = 
> r.id")
> df.queryExecution.executedPlan       // 0 shuffles, 2 GroupPartitionsExec
> withSQLConf(SQLConf.V2_BUCKETING_ENABLED.key -> "false") {
>   df.collect()
> }
> {code}
> {noformat}
> java.lang.ClassCastException: class 
> org.apache.spark.sql.catalyst.plans.physical.UnknownPartitioning
>   cannot be cast to class org.apache.spark.sql.catalyst.expressions.Expression
> {noformat}
> The planner dropped both shuffles on the strength of the key-grouped layout 
> and inserted a {{GroupPartitionsExec}} on each side. Those nodes ask the 
> child for its partitioning again at execution, 
> {{GroupPartitionsExec.grouping}} casts it to {{Partitioning with 
> Expression}}, and by then the scan reports {{UnknownPartitioning}}.
> *Measured on master, {{branch-4.3}} and {{branch-4.2}}*: the same exception 
> on all three, with the same plan shape (0 shuffles, 2 
> {{GroupPartitionsExec}}). {{branch-4.1}} and older do not have 
> {{GroupPartitionsExec}} at all, so the crash site does not exist there.
> h2. Fix
> Make it a {{lazy val}}, so a node answers for its whole life what it answered 
> the planner.
> The value is fixed for the instance otherwise: {{keyGroupedPartitioning}} is 
> a constructor field, every implementation of {{inputPartitions}} is already a 
> {{lazy val}} ({{BatchScanExec}}, {{MicroBatchScanExec}}, 
> {{ContinuousScanExec}}, {{RealTimeStreamScanExec}}), and 
> {{BatchScanExec.filteredPartitions}} derives a new sequence rather than 
> replacing {{inputPartitions}}. {{FileSourceScanExec}}, the V1 twin, is 
> already {{override lazy val (outputPartitioning, outputOrdering)}} over a 
> conf-derived {{bucketedScan}} {{lazy val}}, so this aligns V2 with V1.
> h2. It also removes repeated work
> Not the reason for the ticket, but worth recording. As a {{def}} the 
> key-grouped arm sorts every partition key and hands them to 
> {{KeyedPartitioning.apply}}, which wraps each one and runs a {{distinct}}. 
> Instrumenting the body and running {{KeyGroupedPartitioningSuite}} counted 
> *33,051 executions as a {{def}} against 1,262 as a {{lazy val}}*, so 26 per 
> scan instance instead of one. Separately, in a microbenchmark over a 
> two-column key, one call measured 35 us at 100 partitions, 124 us at 1,000 
> and 1,515 us at 10,000.
> h2. Follow-up, not in scope
> Two other nodes recompute {{outputPartitioning}} the same way and are pure 
> performance, so they go separately, master only: 
> {{PartitioningPreservingUnaryExecNode}} (23,512 body executions, 3,663 
> memoized, and the source of 19,105 of the scan's reads) and 
> {{GroupPartitionsExec}} (8,821). {{supportsColumnar}} on this trait repeats 
> too, but it memoizes a connector call.



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