This is an automated email from the ASF dual-hosted git repository.
liuneng1994 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 6853c22b99 [GLUTEN-10992][VL] Fix MatchError for
KeyGroupedPartitioning in native shuffle (#12335)
6853c22b99 is described below
commit 6853c22b992eaac57f6f9a9313b27c3bcc37d599
Author: BRIJ RAJ KISHORE <[email protected]>
AuthorDate: Sun Jul 5 17:35:41 2026 +0530
[GLUTEN-10992][VL] Fix MatchError for KeyGroupedPartitioning in native
shuffle (#12335)
What changes were proposed in this pull request?
When Spark 4.0's V2 bucketing shuffle
(spark.sql.v2.bucketing.shuffle.enabled=true) is used in a join where only one
side reports partitioning, Spark generates a ShuffleExchangeExec with
KeyGroupedPartitioning as its output partitioning.
The default case _ => in VeloxSparkPlanExecApi.genColumnarShuffleExchange
created a ColumnarShuffleExchangeExec for this node without validation. When
the query executed, ExecUtil.genShuffleDependency crashed with a
scala.MatchError because KeyGroupedPartitioning was missing from its exhaustive
match.
Changes:
VeloxSparkPlanExecApi.genColumnarShuffleExchange: add an explicit case _:
KeyGroupedPartitioning => before the default that adds a fallback tag and
returns the vanilla ShuffleExchangeExec. This prevents a
ColumnarShuffleExchangeExec from being created for an unsupported partitioning
type.
ExecUtil.genShuffleDependency: add an explicit wildcard case other => that
throws GlutenNotSupportException instead of the cryptic scala.MatchError, as a
defensive guard for any future unknown partitioning types.
How was this patch tested?
The existing testGluten("SPARK-41471: shuffle one side: only one side
reports partitioning") tests in GlutenKeyGroupedPartitioningSuite (both spark40
and spark41) reproduce the crash exactly — they set
V2_BUCKETING_SHUFFLE_ENABLED=true with only one bucketed side, which triggers a
ShuffleExchangeExec with KeyGroupedPartitioning output and then call
checkAnswer. After this fix these tests pass without MatchError.
Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (https://claude.ai/code)
Related issue: #10992
---
.../backendsapi/velox/VeloxSparkPlanExecApi.scala | 6 +++
.../spark/sql/execution/utils/ExecUtil.scala | 4 ++
.../GlutenKeyGroupedPartitioningSuite.scala | 46 ++++++++++++++++++++++
.../GlutenKeyGroupedPartitioningSuite.scala | 46 ++++++++++++++++++++++
4 files changed, 102 insertions(+)
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala
index d320fa0179..cf4734e85e 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala
@@ -471,6 +471,12 @@ class VeloxSparkPlanExecApi extends SparkPlanExecApi with
Logging {
}
}
}
+ case _: KeyGroupedPartitioning =>
+ FallbackTags.add(
+ shuffle,
+ ValidationResult.failed(
+ "KeyGroupedPartitioning is not supported by Gluten native
shuffle"))
+ shuffle.withNewChildren(child :: Nil)
case _ =>
ColumnarShuffleExchangeExec(shuffle, child, null)
}
diff --git
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/utils/ExecUtil.scala
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/utils/ExecUtil.scala
index dcfa0ee525..d2f6c2a74b 100644
---
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/utils/ExecUtil.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/utils/ExecUtil.scala
@@ -19,6 +19,7 @@ package org.apache.spark.sql.execution.utils
import org.apache.gluten.backendsapi.BackendsApiManager
import org.apache.gluten.columnarbatch.{ColumnarBatches, VeloxColumnarBatches}
import org.apache.gluten.config.ShuffleWriterType
+import org.apache.gluten.exception.GlutenNotSupportException
import org.apache.gluten.iterator.Iterators
import org.apache.gluten.memory.arrow.alloc.ArrowBufferAllocators
import org.apache.gluten.runtime.Runtimes
@@ -172,6 +173,9 @@ object ExecUtil {
// range partitioning fall back to row-based partition id computation
case RangePartitioning(orders, n) =>
new NativePartitioning(GlutenShuffleUtils.RangePartitioningShortName,
n)
+ case other =>
+ throw new GlutenNotSupportException(
+ s"Partitioning $other is not supported by native shuffle")
}
val isRoundRobin = newPartitioning.isInstanceOf[RoundRobinPartitioning] &&
diff --git
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
index afe0cb7969..3826e50cc8 100644
---
a/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
+++
b/gluten-ut/spark40/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
@@ -21,6 +21,7 @@ import
org.apache.gluten.execution.SortMergeJoinExecTransformer
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, GlutenSQLTestsBaseTrait, Row}
+import org.apache.spark.sql.catalyst.plans.physical.KeyGroupedPartitioning
import org.apache.spark.sql.connector.catalog.{Column, Identifier,
InMemoryTableCatalog}
import org.apache.spark.sql.connector.distributions.Distributions
import org.apache.spark.sql.connector.expressions.Expressions.{bucket, days,
identity, years}
@@ -1072,6 +1073,51 @@ class GlutenKeyGroupedPartitioningSuite
}
}
+ testGluten(
+ "GLUTEN-10992: KeyGroupedPartitioning shuffle falls back to vanilla
Spark") {
+ 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))")
+
+ // With V2 bucketing shuffle enabled and only one side reporting
partitioning, Spark
+ // shuffles the other side with a ShuffleExchangeExec whose output
partitioning is
+ // KeyGroupedPartitioning. Gluten native shuffle does not support it, so
the exchange
+ // must fall back to vanilla Spark. Offloading it to
ColumnarShuffleExchangeExec would
+ // crash with a scala.MatchError in ExecUtil.genShuffleDependency
(GLUTEN-10992).
+ withSQLConf(SQLConf.V2_BUCKETING_SHUFFLE_ENABLED.key -> "true") {
+ val df = createJoinTestDF(Seq("id" -> "item_id"))
+ val plan = df.queryExecution.executedPlan
+
+ val keyGroupedShuffles = collect(plan) {
+ case s: ShuffleExchangeExec
+ if s.outputPartitioning.isInstanceOf[KeyGroupedPartitioning] =>
+ s
+ }
+ assert(
+ keyGroupedShuffles.nonEmpty,
+ "KeyGroupedPartitioning shuffle should fall back to a vanilla
ShuffleExchangeExec")
+
+ val columnarKeyGroupedShuffles = collectAllShuffles(plan)
+ .filter(_.outputPartitioning.isInstanceOf[KeyGroupedPartitioning])
+ assert(
+ columnarKeyGroupedShuffles.isEmpty,
+ "KeyGroupedPartitioning must not be offloaded to
ColumnarShuffleExchangeExec")
+
+ checkAnswer(df, Seq(Row(1, "aa", 40.0, 42.0), Row(3, "bb", 10.0, 19.5)))
+ }
+ }
+
testGluten("SPARK-41471: shuffle one side: only one side reports
partitioning") {
val items_partitions = Array(identity("id"))
createTable(items, itemsColumns, items_partitions)
diff --git
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
index 8e2e4ca47f..5f6793e0e8 100644
---
a/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
+++
b/gluten-ut/spark41/src/test/scala/org/apache/spark/sql/connector/GlutenKeyGroupedPartitioningSuite.scala
@@ -21,6 +21,7 @@ import
org.apache.gluten.execution.SortMergeJoinExecTransformer
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, GlutenSQLTestsBaseTrait, Row}
+import org.apache.spark.sql.catalyst.plans.physical.KeyGroupedPartitioning
import org.apache.spark.sql.connector.catalog.{Column, Identifier,
InMemoryTableCatalog}
import org.apache.spark.sql.connector.distributions.Distributions
import org.apache.spark.sql.connector.expressions.Expressions.{bucket, days,
identity, years}
@@ -1072,6 +1073,51 @@ class GlutenKeyGroupedPartitioningSuite
}
}
+ testGluten(
+ "GLUTEN-10992: KeyGroupedPartitioning shuffle falls back to vanilla
Spark") {
+ 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))")
+
+ // With V2 bucketing shuffle enabled and only one side reporting
partitioning, Spark
+ // shuffles the other side with a ShuffleExchangeExec whose output
partitioning is
+ // KeyGroupedPartitioning. Gluten native shuffle does not support it, so
the exchange
+ // must fall back to vanilla Spark. Offloading it to
ColumnarShuffleExchangeExec would
+ // crash with a scala.MatchError in ExecUtil.genShuffleDependency
(GLUTEN-10992).
+ withSQLConf(SQLConf.V2_BUCKETING_SHUFFLE_ENABLED.key -> "true") {
+ val df = createJoinTestDF(Seq("id" -> "item_id"))
+ val plan = df.queryExecution.executedPlan
+
+ val keyGroupedShuffles = collect(plan) {
+ case s: ShuffleExchangeExec
+ if s.outputPartitioning.isInstanceOf[KeyGroupedPartitioning] =>
+ s
+ }
+ assert(
+ keyGroupedShuffles.nonEmpty,
+ "KeyGroupedPartitioning shuffle should fall back to a vanilla
ShuffleExchangeExec")
+
+ val columnarKeyGroupedShuffles = collectAllShuffles(plan)
+ .filter(_.outputPartitioning.isInstanceOf[KeyGroupedPartitioning])
+ assert(
+ columnarKeyGroupedShuffles.isEmpty,
+ "KeyGroupedPartitioning must not be offloaded to
ColumnarShuffleExchangeExec")
+
+ checkAnswer(df, Seq(Row(1, "aa", 40.0, 42.0), Row(3, "bb", 10.0, 19.5)))
+ }
+ }
+
testGluten("SPARK-41471: shuffle one side: only one side reports
partitioning") {
val items_partitions = Array(identity("id"))
createTable(items, itemsColumns, items_partitions)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]