-
Notifications
You must be signed in to change notification settings - Fork 29.4k
[SPARK-59249][SQL] Take the grouped key-row ordering from the shared InternalRowComparableWrapper cache #58523
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -115,6 +115,9 @@ object InternalRowComparableWrapper { | |
| new InternalRowComparableWrapper(partitionRow, partitionExpression.map(_.dataType)) | ||
| } | ||
|
|
||
| /** The cached ordering a wrapper of these `dataTypes` compares its rows with in `equals`. */ | ||
| def orderingFor(dataTypes: Seq[DataType]): BaseOrdering = orderingCache.get(dataTypes) | ||
|
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. One pre-existing property of this cache that the PR extends to the sort/group sites: the key is
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. Good catch, and I had not traced that. |
||
|
|
||
| /** Creates a shared factory method for a given row schema to avoid excessive cache lookups. */ | ||
| def getInternalRowComparableWrapperFactory( | ||
| dataTypes: Seq[DataType]): InternalRow => InternalRowComparableWrapper = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* | ||
| * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| * contributor license agreements. See the NOTICE file distributed with | ||
| * this work for additional information regarding copyright ownership. | ||
| * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.spark.sql.catalyst.util | ||
|
|
||
| import org.apache.spark.SparkFunSuite | ||
| import org.apache.spark.sql.catalyst.InternalRow | ||
| import org.apache.spark.sql.catalyst.plans.physical.KeyedPartitioning | ||
| import org.apache.spark.sql.types.{IntegerType, LongType} | ||
|
|
||
| class InternalRowComparableWrapperSuite extends SparkFunSuite { | ||
|
|
||
| test("SPARK-59249: the grouped key layout and wrapper equality hold one ordering instance") { | ||
| // Identity is the property to assert, because behaviour is not what changes here: both sides | ||
| // were already built by the same function, so they already compared the same way. What one | ||
| // instance buys is that neither side can later be given a definition the other does not have. | ||
| // `InternalRowComparableWrapper.equals` compares its rows with the instance below, and | ||
| // `KeyedPartitioning` sorts and groups partition keys with it. The two type lists are built | ||
| // separately, so this pins the shared cache as well. | ||
| val wrapper = InternalRowComparableWrapper | ||
| .getInternalRowComparableWrapperFactory(Seq(IntegerType, LongType))(InternalRow(1, 2L)) | ||
|
|
||
| assert(KeyedPartitioning.groupedKeyRowOrdering(Seq(IntegerType, LongType)) eq wrapper.ordering) | ||
| } | ||
| } |
| 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, RowOrdering, SortOrder} | ||
| import org.apache.spark.sql.catalyst.expressions.{Ascending, Expression, SortOrder} | ||
| 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 | ||
|
|
@@ -92,8 +92,7 @@ trait DataSourceV2ScanExecBase | |
| keyGroupedPartitioning match { | ||
|
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. The 27-calls-per-scan count that motivates this PR comes from
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, and we arrived at the same place from the other direction while measuring this PR, so it is already filed as SPARK-59252 and in progress. Measured by instrumenting the body and running |
||
| case Some(exprs) if conf.v2BucketingEnabled && KeyedPartitioning.supportsExpressions(exprs) && | ||
| inputPartitions.nonEmpty && inputPartitions.forall(_.isInstanceOf[HasPartitionKey]) => | ||
| val dataTypes = exprs.map(_.dataType) | ||
| val rowOrdering = RowOrdering.createNaturalAscendingOrdering(dataTypes) | ||
| val rowOrdering = KeyedPartitioning.groupedKeyRowOrdering(exprs.map(_.dataType)) | ||
| val partitionKeys = | ||
| inputPartitions.map(_.asInstanceOf[HasPartitionKey].partitionKey()).sorted(rowOrdering) | ||
| KeyedPartitioning(exprs, partitionKeys) | ||
|
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:
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. Right. The clean form needs |
||
|
|
||
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.
The caveat in the description is accurate, and I checked that nothing in the current suites hits it: every
SharedSparkSessionsuite runs underCODEGEN_ONLYwith fallback off,NO_CODEGENappears only insidewithSQLConfblocks that never build aKeyedPartitioning, and no other construction-time conf affects comparison results (collation is already part ofStringType). Two things it does change for these four sites, which used to re-read the mode per call: the first loader in the JVM decides codegen vs interpreted for the rest of the JVM's life, and inFALLBACKmode a transient codegen failure pins the interpreted instance permanently. If you'd rather keep per-mode fidelity, keying the cache on(SQLConf.get.codegenFactoryMode, dataTypes)in the load path is cheap and keeps the identity the new test asserts within any one mode. Accepting the caveat as written is also defensible.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.
Thank you for checking the suites. I am taking the caveat as written rather than keying on the mode. The two orderings agree semantically, so what is at stake is coverage of the interpreted path, not a result.
Your
FALLBACKpoint is the sharper half: a transient codegen failure would pin the interpreted instance for the JVM's life. If that ever bites,(codegenFactoryMode, dataTypes)is the change and it stays cheap.