-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-24242][SQL] RangeExec should have correct outputOrdering and outputPartitioning #21291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
30b42d5
86c800c
6039094
015e2ad
3a14bd6
59499ad
f93738b
4bcac26
b317777
f41fc14
14a3402
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -345,6 +345,20 @@ case class RangeExec(range: org.apache.spark.sql.catalyst.plans.logical.Range) | |
|
|
||
| override val output: Seq[Attribute] = range.output | ||
|
|
||
| override def outputOrdering: Seq[SortOrder] = range.outputOrdering | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since we are here, shall we also implement
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. |
||
|
|
||
| override def outputPartitioning: Partitioning = { | ||
| if (numElements > 0) { | ||
| if (numSlices == 1) { | ||
| SinglePartition | ||
| } else { | ||
| RangePartitioning(outputOrdering, numSlices) | ||
| } | ||
| } else { | ||
| UnknownPartitioning(0) | ||
| } | ||
| } | ||
|
|
||
| override lazy val metrics = Map( | ||
| "numOutputRows" -> SQLMetrics.createMetric(sparkContext, "number of output rows")) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,9 @@ class ConfigBehaviorSuite extends QueryTest with SharedSQLContext { | |
| def computeChiSquareTest(): Double = { | ||
| val n = 10000 | ||
| // Trigger a sort | ||
| val data = spark.range(0, n, 1, 1).sort('id.desc) | ||
| // Range has range partitioning in its output now. To have a range shuffle, we | ||
| // need to run a repartition first. | ||
| val data = spark.range(0, n, 1, 1).repartition(10).sort('id.desc) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry, I am just curious, why is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test requires a range shuffle. Previously For now
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also confused here, the range output ordering is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because range reports it is just one partition now?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then can we change the code to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test uses If we change it from 1 to 10 partition, the chi-sq value will changed too. Should we do this?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, isn't
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good point. This is query plan and partition size for
Because
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i see, so the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By Here we need a redistribution on data to make sampling difficult. Previously, a repartition is added automatically before |
||
| .selectExpr("SPARK_PARTITION_ID() pid", "id").as[(Int, Long)].collect() | ||
|
|
||
| // Compute histogram for the number of records per partition post sort | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ import org.apache.spark.sql.{execution, Row} | |
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.expressions._ | ||
| import org.apache.spark.sql.catalyst.plans.{Cross, FullOuter, Inner, LeftOuter, RightOuter} | ||
| import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Repartition, Sort} | ||
| import org.apache.spark.sql.catalyst.plans.logical.{LogicalPlan, Range, Repartition, Sort} | ||
| import org.apache.spark.sql.catalyst.plans.physical._ | ||
| import org.apache.spark.sql.execution.columnar.InMemoryRelation | ||
| import org.apache.spark.sql.execution.exchange.{EnsureRequirements, ReusedExchangeExec, ReuseExchange, ShuffleExchangeExec} | ||
|
|
@@ -621,6 +621,31 @@ class PlannerSuite extends SharedSQLContext { | |
| requiredOrdering = Seq(orderingA, orderingB), | ||
| shouldHaveSort = true) | ||
| } | ||
|
|
||
| test("SPARK-24242: RangeExec should have correct output ordering and partitioning") { | ||
| val df = spark.range(10) | ||
| val rangeExec = df.queryExecution.executedPlan.collect { | ||
| case r: RangeExec => r | ||
| } | ||
| val range = df.queryExecution.optimizedPlan.collect { | ||
| case r: Range => r | ||
| } | ||
| assert(rangeExec.head.outputOrdering == range.head.outputOrdering) | ||
| assert(rangeExec.head.outputPartitioning == | ||
| RangePartitioning(rangeExec.head.outputOrdering, df.rdd.getNumPartitions)) | ||
|
|
||
| val rangeInOnePartition = spark.range(1, 10, 1, 1) | ||
| val rangeExecInOnePartition = rangeInOnePartition.queryExecution.executedPlan.collect { | ||
| case r: RangeExec => r | ||
| } | ||
| assert(rangeExecInOnePartition.head.outputPartitioning == SinglePartition) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we also add a test case for the 0 partition case?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. |
||
|
|
||
| val rangeInZeroPartition = spark.range(-10, -9, -20, 1) | ||
| val rangeExecInZeroPartition = rangeInZeroPartition.queryExecution.executedPlan.collect { | ||
| case r: RangeExec => r | ||
| } | ||
| assert(rangeExecInZeroPartition.head.outputPartitioning == UnknownPartitioning(0)) | ||
| } | ||
| } | ||
|
|
||
| // Used for unit-testing EnsureRequirements | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,7 +55,9 @@ class WholeStageCodegenSuite extends QueryTest with SharedSQLContext { | |
| val plan = df.queryExecution.executedPlan | ||
| assert(plan.find(p => | ||
| p.isInstanceOf[WholeStageCodegenExec] && | ||
| p.asInstanceOf[WholeStageCodegenExec].child.isInstanceOf[HashAggregateExec]).isDefined) | ||
| p.asInstanceOf[WholeStageCodegenExec].child.collect { | ||
|
||
| case h: HashAggregateExec => h | ||
| }.nonEmpty).isDefined) | ||
| assert(df.collect() === Array(Row(0, 1), Row(1, 1), Row(2, 1))) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,14 +34,13 @@ class DebuggingSuite extends SparkFunSuite with SharedSQLContext { | |
|
|
||
| test("debugCodegen") { | ||
| val res = codegenString(spark.range(10).groupBy("id").count().queryExecution.executedPlan) | ||
|
||
| assert(res.contains("Subtree 1 / 2")) | ||
| assert(res.contains("Subtree 2 / 2")) | ||
| assert(res.contains("Subtree 1 / 1")) | ||
| assert(res.contains("Object[]")) | ||
| } | ||
|
|
||
| test("debugCodegenStringSeq") { | ||
| val res = codegenStringSeq(spark.range(10).groupBy("id").count().queryExecution.executedPlan) | ||
| assert(res.length == 2) | ||
| assert(res.length == 1) | ||
|
||
| assert(res.forall{ case (subtree, code) => | ||
| subtree.contains("Range") && code.contains("Object[]")}) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not just
orderBy(df.id)? and why was this not failing before this fix?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simply said, the data ordering between
result3andexpect3are different now.Previous query plan for two queries:
Both have
Exchange hashpartitioningwhich produces the same data distribution previously. NoticeSortdoesn't change data ordering because 200 partitions make sparse distribution.Current query plan:
Exchangeis not there anymore. They have same data distribution. But nowSortchanges data ordering.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for your detailed explanation. Anyway, can we just use
orderBy(df.id)instead oforderBy(df.id, df.v % 2)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are already ordered by
df.id. This is the partial data:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I see now, sorry, thanks.