This is an automated email from the ASF dual-hosted git repository.
viirya pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git
The following commit(s) were added to refs/heads/main by this push:
new 2d95fea7 fix: Fallback to Spark for unsupported partitioning (#759)
2d95fea7 is described below
commit 2d95fea7890cf1ceca6b16004ac233d7188e8421
Author: Liang-Chi Hsieh <[email protected]>
AuthorDate: Fri Aug 2 06:03:08 2024 -0700
fix: Fallback to Spark for unsupported partitioning (#759)
* fix: Fallback to Spark for unsupported partitioning
* fix
* Move test
* For review
---
.../apache/comet/CometSparkSessionExtensions.scala | 14 ++-
.../org/apache/comet/serde/QueryPlanSerde.scala | 65 ++++++++---
.../apache/comet/exec/CometShuffle4_0Suite.scala | 122 +++++++++++++++++++++
3 files changed, 177 insertions(+), 24 deletions(-)
diff --git
a/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
b/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
index 028ea063..96ec23fa 100644
--- a/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
+++ b/spark/src/main/scala/org/apache/comet/CometSparkSessionExtensions.scala
@@ -212,7 +212,7 @@ class CometSparkSessionExtensions
case s: ShuffleExchangeExec
if (!s.child.supportsColumnar || isCometPlan(s.child)) &&
isCometJVMShuffleMode(
conf) &&
- QueryPlanSerde.supportPartitioningTypes(s.child.output)._1 &&
+ QueryPlanSerde.supportPartitioningTypes(s.child.output,
s.outputPartitioning)._1 &&
!isShuffleOperator(s.child) =>
logInfo("Comet extension enabled for JVM Columnar Shuffle")
CometShuffleExchangeExec(s, shuffleType = CometColumnarShuffle)
@@ -769,7 +769,7 @@ class CometSparkSessionExtensions
// convert it to CometColumnarShuffle,
case s: ShuffleExchangeExec
if isCometShuffleEnabled(conf) && isCometJVMShuffleMode(conf) &&
- QueryPlanSerde.supportPartitioningTypes(s.child.output)._1 &&
+ QueryPlanSerde.supportPartitioningTypes(s.child.output,
s.outputPartitioning)._1 &&
!isShuffleOperator(s.child) =>
logInfo("Comet extension enabled for JVM Columnar Shuffle")
@@ -789,20 +789,22 @@ class CometSparkSessionExtensions
case s: ShuffleExchangeExec =>
val isShuffleEnabled = isCometShuffleEnabled(conf)
+ val outputPartitioning = s.outputPartitioning
val reason = getCometShuffleNotEnabledReason(conf).getOrElse("no
reason available")
val msg1 = createMessage(!isShuffleEnabled, s"Comet shuffle is not
enabled: $reason")
val columnarShuffleEnabled = isCometJVMShuffleMode(conf)
val msg2 = createMessage(
isShuffleEnabled && !columnarShuffleEnabled && !QueryPlanSerde
- .supportPartitioning(s.child.output, s.outputPartitioning)
+ .supportPartitioning(s.child.output, outputPartitioning)
._1,
"Native shuffle: " +
- s"${QueryPlanSerde.supportPartitioning(s.child.output,
s.outputPartitioning)._2}")
+ s"${QueryPlanSerde.supportPartitioning(s.child.output,
outputPartitioning)._2}")
val msg3 = createMessage(
isShuffleEnabled && columnarShuffleEnabled && !QueryPlanSerde
- .supportPartitioningTypes(s.child.output)
+ .supportPartitioningTypes(s.child.output, outputPartitioning)
._1,
- s"JVM shuffle:
${QueryPlanSerde.supportPartitioningTypes(s.child.output)._2}")
+ "JVM shuffle: " +
+ s"${QueryPlanSerde.supportPartitioningTypes(s.child.output,
outputPartitioning)._2}")
withInfo(s, Seq(msg1, msg2, msg3).flatten.mkString(","))
s
diff --git a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
index 917916c1..948c534d 100644
--- a/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
+++ b/spark/src/main/scala/org/apache/comet/serde/QueryPlanSerde.scala
@@ -27,7 +27,7 @@ import
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression,
import org.apache.spark.sql.catalyst.expressions.objects.StaticInvoke
import org.apache.spark.sql.catalyst.optimizer.{BuildLeft, BuildRight,
NormalizeNaNAndZero}
import org.apache.spark.sql.catalyst.plans._
-import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning,
Partitioning, SinglePartition}
+import org.apache.spark.sql.catalyst.plans.physical.{HashPartitioning,
Partitioning, RangePartitioning, RoundRobinPartitioning, SinglePartition}
import org.apache.spark.sql.catalyst.util.CharVarcharCodegenUtils
import org.apache.spark.sql.comet.{CometBroadcastExchangeExec,
CometRowToColumnarExec, CometSinkPlaceHolder, DecimalPrecision}
import org.apache.spark.sql.comet.execution.shuffle.CometShuffleExchangeExec
@@ -2895,7 +2895,9 @@ object QueryPlanSerde extends Logging with
ShimQueryPlanSerde with CometExprShim
* Check if the datatypes of shuffle input are supported. This is used for
Columnar shuffle
* which supports struct/array.
*/
- def supportPartitioningTypes(inputs: Seq[Attribute]): (Boolean, String) = {
+ def supportPartitioningTypes(
+ inputs: Seq[Attribute],
+ partitioning: Partitioning): (Boolean, String) = {
def supportedDataType(dt: DataType): Boolean = dt match {
case _: ByteType | _: ShortType | _: IntegerType | _: LongType | _:
FloatType |
_: DoubleType | _: StringType | _: BinaryType | _: TimestampType |
_: DecimalType |
@@ -2919,14 +2921,37 @@ object QueryPlanSerde extends Logging with
ShimQueryPlanSerde with CometExprShim
false
}
- // Check if the datatypes of shuffle input are supported.
var msg = ""
- val supported = inputs.forall(attr => supportedDataType(attr.dataType))
+ val supported = partitioning match {
+ case HashPartitioning(expressions, _) =>
+ val supported =
+ expressions.map(QueryPlanSerde.exprToProto(_,
inputs)).forall(_.isDefined) &&
+ expressions.forall(e => supportedDataType(e.dataType))
+ if (!supported) {
+ msg = s"unsupported Spark partitioning expressions: $expressions"
+ }
+ supported
+ case SinglePartition => true
+ case RoundRobinPartitioning(_) => true
+ case RangePartitioning(orderings, _) =>
+ val supported =
+ orderings.map(QueryPlanSerde.exprToProto(_,
inputs)).forall(_.isDefined) &&
+ orderings.forall(e => supportedDataType(e.dataType))
+ if (!supported) {
+ msg = s"unsupported Spark partitioning expressions: $orderings"
+ }
+ supported
+ case _ =>
+ msg = s"unsupported Spark partitioning:
${partitioning.getClass.getName}"
+ false
+ }
+
if (!supported) {
- msg = s"unsupported Spark partitioning: ${inputs.map(_.dataType)}"
emitWarning(msg)
+ (false, msg)
+ } else {
+ (true, null)
}
- (supported, msg)
}
/**
@@ -2945,23 +2970,27 @@ object QueryPlanSerde extends Logging with
ShimQueryPlanSerde with CometExprShim
false
}
- // Check if the datatypes of shuffle input are supported.
- val supported = inputs.forall(attr => supportedDataType(attr.dataType))
+ var msg = ""
+ val supported = partitioning match {
+ case HashPartitioning(expressions, _) =>
+ val supported =
+ expressions.map(QueryPlanSerde.exprToProto(_,
inputs)).forall(_.isDefined) &&
+ expressions.forall(e => supportedDataType(e.dataType))
+ if (!supported) {
+ msg = s"unsupported Spark partitioning expressions: $expressions"
+ }
+ supported
+ case SinglePartition => true
+ case _ =>
+ msg = s"unsupported Spark partitioning:
${partitioning.getClass.getName}"
+ false
+ }
if (!supported) {
- val msg = s"unsupported Spark partitioning: ${inputs.map(_.dataType)}"
emitWarning(msg)
(false, msg)
} else {
- partitioning match {
- case HashPartitioning(expressions, _) =>
- (expressions.map(QueryPlanSerde.exprToProto(_,
inputs)).forall(_.isDefined), null)
- case SinglePartition => (true, null)
- case other =>
- val msg = s"unsupported Spark partitioning:
${other.getClass.getName}"
- emitWarning(msg)
- (false, msg)
- }
+ (true, null)
}
}
diff --git
a/spark/src/test/spark-4.0/org/apache/comet/exec/CometShuffle4_0Suite.scala
b/spark/src/test/spark-4.0/org/apache/comet/exec/CometShuffle4_0Suite.scala
new file mode 100644
index 00000000..517f3f3d
--- /dev/null
+++ b/spark/src/test/spark-4.0/org/apache/comet/exec/CometShuffle4_0Suite.scala
@@ -0,0 +1,122 @@
+/*
+ * 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.comet.exec
+
+import java.util.Collections
+
+import org.apache.spark.sql.DataFrame
+import org.apache.spark.sql.connector.catalog.{Column, Identifier,
InMemoryCatalog, InMemoryTableCatalog}
+import org.apache.spark.sql.connector.expressions.Expressions.identity
+import org.apache.spark.sql.connector.expressions.Transform
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.{FloatType, LongType, StringType,
TimestampType}
+
+class CometShuffle4_0Suite extends CometColumnarShuffleSuite {
+ override protected val asyncShuffleEnable: Boolean = false
+
+ protected val adaptiveExecutionEnabled: Boolean = true
+
+ override def beforeAll(): Unit = {
+ super.beforeAll()
+ spark.conf.set("spark.sql.catalog.testcat",
classOf[InMemoryCatalog].getName)
+ }
+
+ override def afterAll(): Unit = {
+ spark.sessionState.conf.unsetConf("spark.sql.catalog.testcat")
+ super.afterAll()
+ }
+
+ private val emptyProps: java.util.Map[String, String] = {
+ Collections.emptyMap[String, String]
+ }
+ private val items: String = "items"
+ private val itemsColumns: Array[Column] = Array(
+ Column.create("id", LongType),
+ Column.create("name", StringType),
+ Column.create("price", FloatType),
+ Column.create("arrive_time", TimestampType))
+
+ private val purchases: String = "purchases"
+ private val purchasesColumns: Array[Column] = Array(
+ Column.create("item_id", LongType),
+ Column.create("price", FloatType),
+ Column.create("time", TimestampType))
+
+ protected def catalog: InMemoryCatalog = {
+ val catalog = spark.sessionState.catalogManager.catalog("testcat")
+ catalog.asInstanceOf[InMemoryCatalog]
+ }
+
+ private def createTable(
+ table: String,
+ columns: Array[Column],
+ partitions: Array[Transform],
+ catalog: InMemoryTableCatalog = catalog): Unit = {
+ catalog.createTable(Identifier.of(Array("ns"), table), columns,
partitions, emptyProps)
+ }
+
+ private def selectWithMergeJoinHint(t1: String, t2: String): String = {
+ s"SELECT /*+ MERGE($t1, $t2) */ "
+ }
+
+ private def createJoinTestDF(
+ keys: Seq[(String, String)],
+ extraColumns: Seq[String] = Nil,
+ joinType: String = ""): DataFrame = {
+ val extraColList = if (extraColumns.isEmpty) "" else
extraColumns.mkString(", ", ", ", "")
+ sql(s"""
+ |${selectWithMergeJoinHint("i", "p")}
+ |id, name, i.price as purchase_price, p.price as sale_price
$extraColList
+ |FROM testcat.ns.$items i $joinType JOIN testcat.ns.$purchases p
+ |ON ${keys.map(k => s"i.${k._1} = p.${k._2}").mkString(" AND ")}
+ |ORDER BY id, purchase_price, sale_price $extraColList
+ |""".stripMargin)
+ }
+
+ test("Fallback to Spark for unsupported partitioning") {
+ val items_partitions = Array(identity("id"))
+ createTable(items, itemsColumns, items_partitions)
+
+ sql(
+ s"INSERT INTO testcat.ns.$items VALUES " +
+ "(1, 'aa', 40.0, cast('2020-01-01' as timestamp)), " +
+ "(3, 'bb', 10.0, cast('2020-01-01' as timestamp)), " +
+ "(4, 'cc', 15.5, cast('2020-02-01' as timestamp))")
+
+ createTable(purchases, purchasesColumns, Array.empty)
+ sql(
+ s"INSERT INTO testcat.ns.$purchases VALUES " +
+ "(1, 42.0, cast('2020-01-01' as timestamp)), " +
+ "(3, 19.5, cast('2020-02-01' as timestamp)), " +
+ "(5, 26.0, cast('2023-01-01' as timestamp)), " +
+ "(6, 50.0, cast('2023-02-01' as timestamp))")
+
+ Seq(true, false).foreach { shuffle =>
+ withSQLConf(
+ SQLConf.V2_BUCKETING_ENABLED.key -> "true",
+ "spark.sql.sources.v2.bucketing.shuffle.enabled" -> shuffle.toString,
+ SQLConf.V2_BUCKETING_PUSH_PART_VALUES_ENABLED.key -> "true",
+ SQLConf.V2_BUCKETING_PARTIALLY_CLUSTERED_DISTRIBUTION_ENABLED.key ->
"true") {
+ val df = createJoinTestDF(Seq("id" -> "item_id"))
+ checkSparkAnswer(df)
+ }
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]