Repository navigation
[SPARK-59995][SQL] Drop sort orders that hold a partition transform from a V2 scan's output ordering #59251
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
[SPARK-59995][SQL] Drop sort orders that hold a partition transform from a V2 scan's output ordering #59251
Changes from all commits
791e616
913f317
6eca5ef
f77f37e
b661b1a
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 |
|---|---|---|
|
|
@@ -19,7 +19,7 @@ package org.apache.spark.sql.execution.datasources.v2 | |
|
|
||
| import org.apache.spark.rdd.RDD | ||
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.expressions.{Ascending, Expression, ExpressionSet, SortOrder} | ||
| import org.apache.spark.sql.catalyst.expressions.{Ascending, Expression, ExpressionSet, SortOrder, TransformExpression} | ||
| import org.apache.spark.sql.catalyst.plans.physical | ||
| import org.apache.spark.sql.catalyst.plans.physical.KeyedPartitioning | ||
| import org.apache.spark.sql.catalyst.util.truncatedString | ||
|
|
@@ -144,19 +144,29 @@ trait DataSourceV2ScanExecBase | |
| * is a `KeyedPartitioning` and `spark.sql.sources.v2.bucketing.partitionKeyOrdering.enabled` | ||
| * is on, each partition contains rows where the key expressions evaluate to a single constant | ||
| * value, so the data is trivially sorted by those expressions within the partition. | ||
| * | ||
| * Either way, a sort order that holds a partition transform is dropped, even on a partition key. | ||
| * In a reported ordering it also ends the leading run, like a sort order over a pruned column. | ||
| * Dropping it loses nothing today when Spark can call the transform's function, since no | ||
| * operator then requires an ordering over the transform. The write path sorts by the function | ||
| * call instead. Keeping such a sort order would add comparisons nobody uses. Dropping it also | ||
| * handles a transform Spark cannot evaluate. Revisit this if an ordering over a transform | ||
| * becomes a real requirement. | ||
| */ | ||
| override def outputOrdering: Seq[SortOrder] = { | ||
| def holdsTransform(e: Expression): Boolean = e.exists(_.isInstanceOf[TransformExpression]) | ||
|
Member
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. Minor: the transform sort orders are dropped only here, in the physical Could we file a follow-up JIRA to leave the transform sort orders out of those logical comparisons as well?
Contributor
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. Agreed. I filed SPARK-60083 for it. |
||
| (ordering, outputPartitioning) match { | ||
| case (Some(o), p) => | ||
| val (prefix, rest) = o.span(_.references.subsetOf(outputSet)) | ||
| val (prefix, rest) = | ||
| o.span(order => order.references.subsetOf(outputSet) && !holdsTransform(order.child)) | ||
|
Member
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. Minor: a sort order on a transform that is itself a partition key ends the leading run here, although it is constant within each partition, which is the reason L142-143 give for keeping the sort orders on a partition key. So the sort orders after it that still hold are dropped. With keys Could we skip a sort order on a partition key instead of ending the run there, e.g.
Contributor
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. Agreed. I filed SPARK-60043 for it. |
||
| p match { | ||
| case k: KeyedPartitioning if rest.nonEmpty => | ||
| val keyExprs = ExpressionSet(k.expressions) | ||
| val keyExprs = ExpressionSet(k.expressions.filterNot(holdsTransform)) | ||
|
Member
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. Nit: BoundFunction.java:121 still lists "retaining a reported ordering that matches the partitioning" among the benefits of a semantic Could we drop that bullet, unless item 15 brings back an
Contributor
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. Dropped in 6eca5ef, but I put a different bullet in its place. |
||
| prefix ++ rest.filter(order => keyExprs.contains(order.child)) | ||
| case _ => prefix | ||
| } | ||
| case (_, k: KeyedPartitioning) if conf.v2BucketingPartitionKeyOrderingEnabled => | ||
|
Member
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. Minor: the PR description does not say since when the failure exists or where the ordering change is on by default. The k-way merge (SPARK-55715) and the key-derived ordering (SPARK-56241) are both in 4.2.0, so the failure exists since 4.2.0 with Could we add both to the user-facing section?
Contributor
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. Added both to the user-facing section. I measured the sort aggregate: |
||
| k.expressions.map(SortOrder(_, Ascending)) | ||
| k.expressions.filterNot(holdsTransform).map(SortOrder(_, Ascending)) | ||
|
Member
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. Minor: with transform keys left out here, some docs now overstate what the derived ordering gives. The 4.4 migration note (docs/sql-migration-guide.md:45) says the scan "now also reports itself sorted by its partition key expressions", and the Could we say in those docs that partition transforms are left out, e.g. "sorted by those of its partition key expressions that are columns (a partition transform such as
Contributor
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. Done in 6eca5ef, in the conf doc, the tuning guide, the 4.4 migration note and the |
||
| case _ => Seq.empty | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,6 @@ import org.apache.spark.{Partition, SparkException} | |
| import org.apache.spark.rdd.{CoalescedRDD, PartitionCoalescer, PartitionGroup, RDD, SortedMergeCoalescedRDD} | ||
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.expressions._ | ||
| import org.apache.spark.sql.catalyst.expressions.codegen.GenerateOrdering | ||
| import org.apache.spark.sql.catalyst.plans.QueryPlan | ||
| import org.apache.spark.sql.catalyst.plans.physical.{IdentityReducer, KeyedPartitioning, KeyLayout, KeyReducer, Partitioning, PartitioningCollection, REPLICATED_FOR_JOIN, UngroupingOrigin, UnknownPartitioning} | ||
| import org.apache.spark.sql.catalyst.util.{truncatedString, InternalRowComparableWrapper} | ||
|
|
@@ -277,8 +276,8 @@ case class GroupPartitionsExec( | |
| } | ||
|
|
||
| /** | ||
| * The ordering used by the k-way merge in [[SortedMergeCoalescedRDD]]. The generated comparator | ||
| * ([[GenerateOrdering]]) only needs each [[SortOrder]]'s sort key (child, direction, null | ||
| * The ordering used by the k-way merge in [[SortedMergeCoalescedRDD]]. The comparator that | ||
| * [[RowOrdering]] builds only needs each [[SortOrder]]'s sort key (child, direction, null | ||
| * ordering), so `sameOrderExpressions` -- planner-only metadata that would otherwise be | ||
| * serialized with the RDD in every task -- is dropped. | ||
| */ | ||
|
|
@@ -345,7 +344,7 @@ case class GroupPartitionsExec( | |
| sparkContext.emptyRDD | ||
| } else if (usesSortedMerge) { | ||
| val partitionCoalescer = new GroupedPartitionCoalescer(groupedPartitions.map(_._2)) | ||
| val rowOrdering = new LazyCodeGenOrdering(kWayMergeOrdering, child.output) | ||
| val rowOrdering = new LazyRowOrdering(kWayMergeOrdering, child.output) | ||
|
Member
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.
Could we file a follow-up JIRA to freeze the merge ordering when
Contributor
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. Agreed. I filed SPARK-60044 for it. |
||
| new SortedMergeCoalescedRDD[InternalRow]( | ||
| child.execute(), | ||
| groupedPartitions.size, | ||
|
|
@@ -399,11 +398,9 @@ case class GroupPartitionsExec( | |
| outputPartitioning match { | ||
| case p: Partitioning with Expression | ||
| if reducers.isEmpty && conf.v2BucketingPreserveKeyOrderingOnCoalesceEnabled => | ||
| // Without reducers all merged partitions share the same original key value, so the key | ||
| // expressions remain constant within the output partition. The child's outputOrdering | ||
| // should already be in sync with the partitioning (either reported by the source or | ||
| // derived from it in DataSourceV2ScanExecBase), so we only need to keep the sort orders | ||
| // whose expression is a partition key expression -- all others are lost by concatenation. | ||
| // Without reducers all merged partitions share the same original key value, so the sort | ||
| // orders on key expressions still hold. The transform keys match nothing here, since | ||
| // `DataSourceV2ScanExecBase.outputOrdering` drops every sort order over a transform. | ||
| val keyedPartitionings = p.collect { case k: KeyedPartitioning => k } | ||
| val keyExprs = ExpressionSet(keyedPartitionings.flatMap(_.expressions)) | ||
| child.outputOrdering.filter(order => keyExprs.contains(order.child)) | ||
|
|
@@ -820,15 +817,15 @@ class GroupedPartitionCoalescer( | |
| } | ||
|
|
||
| /** | ||
| * A serializable [[Ordering]] for [[InternalRow]] that generates code-compiled comparison logic | ||
| * lazily on first use. The [[SortOrder]] expressions and output schema are serialized with the | ||
| * RDD; the generated comparator is rebuilt on the executor on first comparison via | ||
| * [[GenerateOrdering]]. | ||
| * A serializable [[Ordering]] for [[InternalRow]]. The [[SortOrder]] expressions and output schema | ||
| * are serialized with the RDD. The comparator is built on the executor, at the first comparison. | ||
| * [[RowOrdering]] builds it, as for `SortExec`. It tries generated code first and falls back to | ||
| * interpreted evaluation when that fails. | ||
| */ | ||
| private class LazyCodeGenOrdering( | ||
| private class LazyRowOrdering( | ||
| sortOrders: Seq[SortOrder], | ||
| schema: Seq[Attribute]) extends Ordering[InternalRow] with Serializable { | ||
| @transient private lazy val generated: Ordering[InternalRow] = | ||
| GenerateOrdering.generate(sortOrders, schema) | ||
| override def compare(x: InternalRow, y: InternalRow): Int = generated.compare(x, y) | ||
| @transient private lazy val ordering: Ordering[InternalRow] = | ||
| RowOrdering.create(sortOrders, schema) | ||
| override def compare(x: InternalRow, y: InternalRow): Int = ordering.compare(x, y) | ||
| } | ||
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.
Nit: this makes the coalescing-branch comment at GroupPartitionsExec.scala:391-395 stale. It says the child's outputOrdering "should already be in sync with the partitioning (either reported by the source or derived from it in DataSourceV2ScanExecBase)", but this scan now leaves out every transform key, so the transform entries of
keyExprsthere never match. For example, keys[years(ts)]withpreserveKeyOrderingOnCoalesceon reported[years(ts)]before and report[]now. That file is already in this PR.Could that comment say the child's ordering is in sync with the partitioning's column keys, since this scan drops every sort order over a partition transform?
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.
Reworded in 6eca5ef.