[ 
https://issues.apache.org/jira/browse/SPARK-49030?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17870359#comment-17870359
 ] 

Gera Shegalov edited comment on SPARK-49030 at 8/2/24 5:03 AM:
---------------------------------------------------------------

I think UNION ALL of the same CTE  which is LIMIT 1 out of spark.range(100) is 
a better illustration of the nondeterminism  through the duplicate CTE 
evaluation.

Using Spark 3.5.1:
{code:java}
$ ~/dist/spark-3.5.1-bin-hadoop3/bin/pyspark --master local-cluster[4,1,1024] 
Welcome to                                                                      
                                                                                
                               
      ____              __                                                      
                                                                                
                               
     / __/__  ___ _____/ /__                                                    
                                                                                
                               
    _\ \/ _ \/ _ `/ __/  '_/                                                    
                                                                                
                               
   /__ / .__/\_,_/_/ /_/\_\   version 3.5.1                                     
                                                                                
                               
      /_/                                                                       
                                                                                
                               
                                                                                
                                                                                
                               
Using Python version 3.10.12 (main, Mar 22 2024 16:50:05)                       
                                                                                
                               
Spark context Web UI available at http://view-localhost:4040                    
                                                                                
                               
Spark context available as 'sc' (master = local-cluster[4,1,1024], app id = 
app-20240801213303-0000).                                                       
                                   
SparkSession available as 'spark'.                                              
                                                                                
                               
>>> spark.range(100).repartition(100).write.mode("overwrite").parquet("/tmp/range")
>>>                                                                             
>>>                                 
>>> spark.read.parquet("/tmp/range").createOrReplaceTempView('tbl')             
>>>                                                                             
>>>                                    
>>> df = sql("WITH c AS (SELECT * FROM tbl LIMIT 1) SELECT * FROM c UNION ALL 
>>> SELECT * FROM c")                                                           
>>>                                      
>>> df.collect()                                                                
>>>                                                                             
>>>                                    
[Row(id=86), Row(id=14)]
>>> df.explain("extended")                                                      
>>>                 
== Parsed Logical Plan ==
CTE [c]
:  +- 'SubqueryAlias c
:     +- 'GlobalLimit 1
:        +- 'LocalLimit 1
:           +- 'Project [*]
:              +- 'UnresolvedRelation [tbl], [], false
+- 'Union false, false
   :- 'Project [*]
   :  +- 'UnresolvedRelation [c], [], false
   +- 'Project [*]
      +- 'UnresolvedRelation [c], [], false

== Analyzed Logical Plan ==
id: bigint
WithCTE
:- CTERelationDef 0, false
:  +- SubqueryAlias c
:     +- GlobalLimit 1
:        +- LocalLimit 1
:           +- Project [id#5L]
:              +- SubqueryAlias tbl
:                 +- View (`tbl`, [id#5L])
:                    +- Relation [id#5L] parquet
+- Union false, false
   :- Project [id#5L]
   :  +- SubqueryAlias c
   :     +- CTERelationRef 0, true, [id#5L], false
   +- Project [id#7L]
      +- SubqueryAlias c
         +- CTERelationRef 0, true, [id#7L], false

== Optimized Logical Plan ==
Union false, false
:- GlobalLimit 1
:  +- LocalLimit 1
:     +- Relation [id#5L] parquet
+- GlobalLimit 1
   +- LocalLimit 1
      +- Relation [id#10L] parquet

== Physical Plan ==
AdaptiveSparkPlan isFinalPlan=true
+- == Final Plan ==
   Union
   :- *(3) GlobalLimit 1, 0
   :  +- ShuffleQueryStage 0
   :     +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=79]
   :        +- *(1) LocalLimit 1
   :           +- *(1) ColumnarToRow
   :              +- FileScan parquet [id#5L] Batched: true, DataFilters: [], 
Format: Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], 
ReadSchema: struct<id:bigint>
   +- *(4) GlobalLimit 1, 0
      +- ShuffleQueryStage 2
         +- ReusedExchange [id#10L], Exchange SinglePartition, 
ENSURE_REQUIREMENTS, [plan_id=79]
+- == Initial Plan ==
   Union
   :- GlobalLimit 1, 0
   :  +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=64]
   :     +- LocalLimit 1
   :        +- FileScan parquet [id#5L] Batched: true, DataFilters: [], Format: 
Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], ReadSc
hema: struct<id:bigint>
   +- GlobalLimit 1, 0
      +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=66]
         +- LocalLimit 1
            +- FileScan parquet [id#10L] Batched: true, DataFilters: [], 
Format: Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], ReadS
chema: struct<id:bigint>
{code}
Because GlobalLimit is not part of the reused exchange 
{{ReusedExchange [id#10L|#10L], Exchange SinglePartition, ENSURE_REQUIREMENTS, 
[plan_id=79]}}

There are two non-identical rows in the result: {{ [Row(id=86), Row(id=14)] }}


was (Author: jira.shegalov):
I think UNION ALL of the same CTE  which is LIMIT 1 out of spark.range(100) is 
a better illustration of the nondeterminism  duplicate CTE evaluation.

Using Spark 3.5.1:
{code:java}
$ ~/dist/spark-3.5.1-bin-hadoop3/bin/pyspark --master local-cluster[4,1,1024] 
Welcome to                                                                      
                                                                                
                               
      ____              __                                                      
                                                                                
                               
     / __/__  ___ _____/ /__                                                    
                                                                                
                               
    _\ \/ _ \/ _ `/ __/  '_/                                                    
                                                                                
                               
   /__ / .__/\_,_/_/ /_/\_\   version 3.5.1                                     
                                                                                
                               
      /_/                                                                       
                                                                                
                               
                                                                                
                                                                                
                               
Using Python version 3.10.12 (main, Mar 22 2024 16:50:05)                       
                                                                                
                               
Spark context Web UI available at http://view-localhost:4040                    
                                                                                
                               
Spark context available as 'sc' (master = local-cluster[4,1,1024], app id = 
app-20240801213303-0000).                                                       
                                   
SparkSession available as 'spark'.                                              
                                                                                
                               
>>> spark.range(100).repartition(100).write.mode("overwrite").parquet("/tmp/range")
>>>                                                                             
>>>                                 
>>> spark.read.parquet("/tmp/range").createOrReplaceTempView('tbl')             
>>>                                                                             
>>>                                    
>>> df = sql("WITH c AS (SELECT * FROM tbl LIMIT 1) SELECT * FROM c UNION ALL 
>>> SELECT * FROM c")                                                           
>>>                                      
>>> df.collect()                                                                
>>>                                                                             
>>>                                    
[Row(id=86), Row(id=14)]
>>> df.explain("extended")                                                      
>>>                 
== Parsed Logical Plan ==
CTE [c]
:  +- 'SubqueryAlias c
:     +- 'GlobalLimit 1
:        +- 'LocalLimit 1
:           +- 'Project [*]
:              +- 'UnresolvedRelation [tbl], [], false
+- 'Union false, false
   :- 'Project [*]
   :  +- 'UnresolvedRelation [c], [], false
   +- 'Project [*]
      +- 'UnresolvedRelation [c], [], false

== Analyzed Logical Plan ==
id: bigint
WithCTE
:- CTERelationDef 0, false
:  +- SubqueryAlias c
:     +- GlobalLimit 1
:        +- LocalLimit 1
:           +- Project [id#5L]
:              +- SubqueryAlias tbl
:                 +- View (`tbl`, [id#5L])
:                    +- Relation [id#5L] parquet
+- Union false, false
   :- Project [id#5L]
   :  +- SubqueryAlias c
   :     +- CTERelationRef 0, true, [id#5L], false
   +- Project [id#7L]
      +- SubqueryAlias c
         +- CTERelationRef 0, true, [id#7L], false

== Optimized Logical Plan ==
Union false, false
:- GlobalLimit 1
:  +- LocalLimit 1
:     +- Relation [id#5L] parquet
+- GlobalLimit 1
   +- LocalLimit 1
      +- Relation [id#10L] parquet

== Physical Plan ==
AdaptiveSparkPlan isFinalPlan=true
+- == Final Plan ==
   Union
   :- *(3) GlobalLimit 1, 0
   :  +- ShuffleQueryStage 0
   :     +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=79]
   :        +- *(1) LocalLimit 1
   :           +- *(1) ColumnarToRow
   :              +- FileScan parquet [id#5L] Batched: true, DataFilters: [], 
Format: Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], 
ReadSchema: struct<id:bigint>
   +- *(4) GlobalLimit 1, 0
      +- ShuffleQueryStage 2
         +- ReusedExchange [id#10L], Exchange SinglePartition, 
ENSURE_REQUIREMENTS, [plan_id=79]
+- == Initial Plan ==
   Union
   :- GlobalLimit 1, 0
   :  +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=64]
   :     +- LocalLimit 1
   :        +- FileScan parquet [id#5L] Batched: true, DataFilters: [], Format: 
Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], ReadSc
hema: struct<id:bigint>
   +- GlobalLimit 1, 0
      +- Exchange SinglePartition, ENSURE_REQUIREMENTS, [plan_id=66]
         +- LocalLimit 1
            +- FileScan parquet [id#10L] Batched: true, DataFilters: [], 
Format: Parquet, Location: InMemoryFileIndex(1 paths)[file:/tmp/range], 
PartitionFilters: [], PushedFilters: [], ReadS
chema: struct<id:bigint>
{code}
Because GlobalLimit is not part of the reused exchange 
{{ReusedExchange [id#10L|#10L], Exchange SinglePartition, ENSURE_REQUIREMENTS, 
[plan_id=79]}}

There are two non-identical rows in the result: {{ [Row(id=86), Row(id=14)] }}

> Self join of a CTE seems non-deterministic
> ------------------------------------------
>
>                 Key: SPARK-49030
>                 URL: https://issues.apache.org/jira/browse/SPARK-49030
>             Project: Spark
>          Issue Type: Bug
>          Components: SQL
>    Affects Versions: 4.0.0
>         Environment: Tested with Spark 3.4.1, 3.5.1, and 4.0.0-preview.
>            Reporter: Jihoon Son
>            Priority: Minor
>             Fix For: 3.5.3
>
>         Attachments: screenshot-1.png
>
>
> {code:java}
> WITH c AS (SELECT * FROM customer LIMIT 10)
> SELECT count(*)
> FROM c c1, c c2
> WHERE c1.c_customer_sk > c2.c_customer_sk{code}
> Suppose a self join query on a CTE such as the one above.
> Spark generates a physical plan like the one below for this query.
> {code:java}
> == Physical Plan ==
> AdaptiveSparkPlan isFinalPlan=false
> +- HashAggregate(keys=[], functions=[count(1)], output=[count(1)#194L])
>    +- HashAggregate(keys=[], functions=[partial_count(1)], 
> output=[count#233L])
>       +- Project
>          +- BroadcastNestedLoopJoin BuildRight, Inner, (c_customer_sk#0 > 
> c_customer_sk#214)
>             :- Filter isnotnull(c_customer_sk#0)
>             :  +- GlobalLimit 10, 0
>             :     +- Exchange SinglePartition, ENSURE_REQUIREMENTS, 
> [plan_id=256]
>             :        +- LocalLimit 10
>             :           +- FileScan parquet [c_customer_sk#0] Batched: true, 
> DataFilters: [], Format: Parquet, Location: InMemoryFileIndex(1 
> paths)[file:/some/path/customer], PartitionFilters: [], PushedFilters: [], 
> ReadSchema: struct<c_customer_sk:int>
>             +- BroadcastExchange IdentityBroadcastMode, [plan_id=263]
>                +- Filter isnotnull(c_customer_sk#214)
>                   +- GlobalLimit 10, 0
>                      +- Exchange SinglePartition, ENSURE_REQUIREMENTS, 
> [plan_id=259]
>                         +- LocalLimit 10
>                            +- FileScan parquet [c_customer_sk#214] Batched: 
> true, DataFilters: [], Format: Parquet, Location: InMemoryFileIndex(1 
> paths)[file:/some/path/customer], PartitionFilters: [], PushedFilters: [], 
> ReadSchema: struct<c_customer_sk:int>{code}
> Evaluating this plan produces non-deterministic result because the limit is 
> independently pushed into the two sides of the join. Each limit can produce 
> different data, and thus the join can produce results that vary across runs.
> I understand that the query in question is not deterministic (and thus not 
> very practical) as, due to the nature of the limit in distributed engines, it 
> is not expected to produce the same result anyway across repeated runs. 
> However, I would still expect that the query plan evaluation remains 
> deterministic.
> Per extended analysis as seen below, it seems that the query plan has changed 
> at some point during optimization.
> {code:java}
> == Analyzed Logical Plan ==
> count(1): bigint
> WithCTE
> :- CTERelationDef 2, false
> :  +- SubqueryAlias c
> :     +- GlobalLimit 10
> :        +- LocalLimit 10
> :           +- Project [c_customer_sk#0, c_customer_id#1, 
> c_current_cdemo_sk#2, c_current_hdemo_sk#3, c_current_addr_sk#4, 
> c_first_shipto_date_sk#5, c_first_sales_date_sk#6, c_salutation#7, 
> c_first_name#8, c_last_name#9, c_preferred_cust_flag#10, c_birth_day#11L, 
> c_birth_month#12L, c_birth_year#13L, c_birth_country#14, c_login#15, 
> c_email_address#16, c_last_review_date_sk#17]
> :              +- SubqueryAlias customer
> :                 +- View (`customer`, [c_customer_sk#0, c_customer_id#1, 
> c_current_cdemo_sk#2, c_current_hdemo_sk#3, c_current_addr_sk#4, 
> c_first_shipto_date_sk#5, c_first_sales_date_sk#6, c_salutation#7, 
> c_first_name#8, c_last_name#9, c_preferred_cust_flag#10, c_birth_day#11L, 
> c_birth_month#12L, c_birth_year#13L, c_birth_country#14, c_login#15, 
> c_email_address#16, c_last_review_date_sk#17])
> :                    +- Relation 
> [c_customer_sk#0,c_customer_id#1,c_current_cdemo_sk#2,c_current_hdemo_sk#3,c_current_addr_sk#4,c_first_shipto_date_sk#5,c_first_sales_date_sk#6,c_salutation#7,c_first_name#8,c_last_name#9,c_preferred_cust_flag#10,c_birth_day#11L,c_birth_month#12L,c_birth_year#13L,c_birth_country#14,c_login#15,c_email_address#16,c_last_review_date_sk#17]
>  parquet
> +- Aggregate [count(1) AS count(1)#194L]
>    +- Filter (c_customer_sk#0 > c_customer_sk#176)
>       +- Join Inner
>          :- SubqueryAlias c1
>          :  +- SubqueryAlias c
>          :     +- CTERelationRef 2, true, [c_customer_sk#0, c_customer_id#1, 
> c_current_cdemo_sk#2, c_current_hdemo_sk#3, c_current_addr_sk#4, 
> c_first_shipto_date_sk#5, c_first_sales_date_sk#6, c_salutation#7, 
> c_first_name#8, c_last_name#9, c_preferred_cust_flag#10, c_birth_day#11L, 
> c_birth_month#12L, c_birth_year#13L, c_birth_country#14, c_login#15, 
> c_email_address#16, c_last_review_date_sk#17], false
>          +- SubqueryAlias c2
>             +- SubqueryAlias c
>                +- CTERelationRef 2, true, [c_customer_sk#176, 
> c_customer_id#177, c_current_cdemo_sk#178, c_current_hdemo_sk#179, 
> c_current_addr_sk#180, c_first_shipto_date_sk#181, c_first_sales_date_sk#182, 
> c_salutation#183, c_first_name#184, c_last_name#185, 
> c_preferred_cust_flag#186, c_birth_day#187L, c_birth_month#188L, 
> c_birth_year#189L, c_birth_country#190, c_login#191, c_email_address#192, 
> c_last_review_date_sk#193], false
> == Optimized Logical Plan ==
> Aggregate [count(1) AS count(1)#194L]
> +- Project
>    +- Join Inner, (c_customer_sk#0 > c_customer_sk#214)
>       :- Filter isnotnull(c_customer_sk#0)
>       :  +- GlobalLimit 10
>       :     +- LocalLimit 10
>       :        +- Project [c_customer_sk#0]
>       :           +- Relation 
> [c_customer_sk#0,c_customer_id#1,c_current_cdemo_sk#2,c_current_hdemo_sk#3,c_current_addr_sk#4,c_first_shipto_date_sk#5,c_first_sales_date_sk#6,c_salutation#7,c_first_name#8,c_last_name#9,c_preferred_cust_flag#10,c_birth_day#11L,c_birth_month#12L,c_birth_year#13L,c_birth_country#14,c_login#15,c_email_address#16,c_last_review_date_sk#17]
>  parquet
>       +- Filter isnotnull(c_customer_sk#214)
>          +- GlobalLimit 10
>             +- LocalLimit 10
>                +- Project [c_customer_sk#214]
>                   +- Relation 
> [c_customer_sk#214,c_customer_id#215,c_current_cdemo_sk#216,c_current_hdemo_sk#217,c_current_addr_sk#218,c_first_shipto_date_sk#219,c_first_sales_date_sk#220,c_salutation#221,c_first_name#222,c_last_name#223,c_preferred_cust_flag#224,c_birth_day#225L,c_birth_month#226L,c_birth_year#227L,c_birth_country#228,c_login#229,c_email_address#230,c_last_review_date_sk#231]
>  parquet {code}



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