You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by pt...@apache.org on 2026/03/19 07:15:09 UTC
(spark) branch master updated: [SPARK-56020][SQL] Improve `GroupPartitions` Spark UI
This is an automated email from the ASF dual-hosted git repository.
ptoth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new 2188c9977c58 [SPARK-56020][SQL] Improve `GroupPartitions` Spark UI
2188c9977c58 is described below
commit 2188c9977c581b85e42c285960ed2e416100f65e
Author: Szehon Ho <sz...@gmail.com>
AuthorDate: Thu Mar 19 08:14:52 2026 +0100
[SPARK-56020][SQL] Improve `GroupPartitions` Spark UI
### What changes were proposed in this pull request?
Follow up for https://github.com/apache/spark/pull/54459
1. It handled explain but not UI
2. Reducers could be improved a bit
3. Fix a test typo
### Why are the changes needed?
Improve UX for the new SPJ group operator
### Does this PR introduce _any_ user-facing change?
Adds to Explain Extended, and changes unreleased Spark UI for the GroupPartitions node a bit
### How was this patch tested?
Add unit test
### Was this patch authored or co-authored using generative AI tooling?
Yes, cursor
Closes #54842 from szehon-ho/group_partitions_string_follow.
Authored-by: Szehon Ho <sz...@gmail.com>
Signed-off-by: Peter Toth <pe...@gmail.com>
---
.../sql/connector/catalog/functions/Reducer.java | 8 +++++++
.../datasources/v2/GroupPartitionsExec.scala | 26 +++++++++++++++-------
.../connector/KeyGroupedPartitioningSuite.scala | 22 ++++++++++--------
.../catalog/functions/transformFunctions.scala | 1 +
4 files changed, 40 insertions(+), 17 deletions(-)
diff --git a/sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/functions/Reducer.java b/sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/functions/Reducer.java
index 561d66092d64..54d22057b45a 100644
--- a/sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/functions/Reducer.java
+++ b/sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/functions/Reducer.java
@@ -39,4 +39,12 @@ import org.apache.spark.annotation.Evolving;
@Evolving
public interface Reducer<I, O> {
O reduce(I arg);
+
+ /**
+ * Returns a short string representation for plan display (e.g. explain output).
+ * Defaults to the unqualified class name; implementations may override for a richer description.
+ */
+ default String displayName() {
+ return getClass().getSimpleName();
+ }
}
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala
index d5260cad9c6e..10e59c7b3611 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/GroupPartitionsExec.scala
@@ -25,7 +25,7 @@ import org.apache.spark.rdd.{CoalescedRDD, PartitionCoalescer, PartitionGroup, R
import org.apache.spark.sql.catalyst.InternalRow
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.plans.physical.{KeyedPartitioning, Partitioning}
-import org.apache.spark.sql.catalyst.util.InternalRowComparableWrapper
+import org.apache.spark.sql.catalyst.util.{truncatedString, InternalRowComparableWrapper}
import org.apache.spark.sql.connector.catalog.functions.Reducer
import org.apache.spark.sql.execution.{SparkPlan, UnaryExecNode}
import org.apache.spark.sql.types.DataType
@@ -193,13 +193,23 @@ case class GroupPartitionsExec(
}
override def simpleString(maxFields: Int): String = {
- val joinKeyPositionsString =
- joinKeyPositions.map(p => s" JoinKeyPositions: ${p.mkString("[", ",", "]")}").getOrElse("")
- val expectedPartitionKeysString =
- expectedPartitionKeys.map(ks => s" ExpectedPartitionKeys: ${ks.size}").getOrElse("")
- val reducersString = reducers.map(r => s" Reducers: ${r.count(_.isDefined)}").getOrElse("")
- s"$nodeName$joinKeyPositionsString$expectedPartitionKeysString$reducersString " +
- s"DistributePartitions: $distributePartitions"
+ s"$nodeName${planSummaryParts(maxFields).map(" " + _).mkString("")}"
+ }
+
+ override protected def stringArgs: Iterator[Any] = planSummaryParts(Int.MaxValue)
+
+ private def planSummaryParts(joinKeyMaxFields: Int): Iterator[String] = {
+ val joinKeyStr = joinKeyPositions.map { p =>
+ s"JoinKeyPositions: ${truncatedString(p, "[", ",", "]", joinKeyMaxFields)}"
+ }.iterator
+ val expectedStr = expectedPartitionKeys.map(ks => s"ExpectedPartitionKeys: ${ks.size}")
+ val reducersStr = reducers.map { seq =>
+ val names = seq.map(_.map(_.toString()).getOrElse("identity"))
+ s"Reducers: ${truncatedString(names, "[", ", ", "]", joinKeyMaxFields)}"
+ }
+ val distributeStr = Iterator(s"DistributePartitions: $distributePartitions")
+ joinKeyStr ++ expectedStr ++ reducersStr ++ distributeStr
+
}
}
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala
index f2eb02e03846..c29be45b72fa 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala
@@ -29,7 +29,7 @@ import org.apache.spark.sql.connector.catalog.functions._
import org.apache.spark.sql.connector.distributions.Distributions
import org.apache.spark.sql.connector.expressions._
import org.apache.spark.sql.connector.expressions.Expressions._
-import org.apache.spark.sql.execution.SparkPlan
+import org.apache.spark.sql.execution.{ExtendedMode, FormattedMode, SimpleMode, SparkPlan}
import org.apache.spark.sql.execution.datasources.v2.{BatchScanExec, DataSourceV2ScanRelation, GroupPartitionsExec}
import org.apache.spark.sql.execution.exchange.{ShuffleExchangeExec, ShuffleExchangeLike}
import org.apache.spark.sql.execution.joins.SortMergeJoinExec
@@ -3217,29 +3217,33 @@ class KeyGroupedPartitioningSuite extends DistributionAndOrderingSuiteBase with
}
}
- test("SPARK-55992: GroupPartitions textual representatin in plans") {
+ test("SPARK-55992: GroupPartitions string in simple and extended explain") {
val items_partitions = Array(bucket(4, "id"), years("arrive_time"))
createTable(items, itemsColumns, items_partitions)
-
sql(s"INSERT INTO testcat.ns.$items VALUES (1, 'aa', 10.0, cast('2021-01-01' as timestamp))")
-
val purchases_partitions = Array(bucket(6, "item_id"), years("time"))
createTable(purchases, purchasesColumns, purchases_partitions)
-
sql(s"INSERT INTO testcat.ns.$purchases VALUES (2, 10.0, cast('2021-01-01' as timestamp))")
-
withSQLConf(
SQLConf.REQUIRE_ALL_CLUSTER_KEYS_FOR_CO_PARTITION.key -> "false",
SQLConf.V2_BUCKETING_ALLOW_JOIN_KEYS_SUBSET_OF_PARTITION_KEYS.key -> "true",
SQLConf.V2_BUCKETING_ALLOW_COMPATIBLE_TRANSFORMS.key -> "true") {
val df = sql(
s"""
- |SELECT *
+ |${selectWithMergeJoinHint("i", "p")}
+ |*
|FROM testcat.ns.$items i
|JOIN testcat.ns.$purchases p ON p.item_id = i.id
|""".stripMargin)
- checkKeywordsExistsInExplain(df, keywords = "GroupPartitions JoinKeyPositions: [0] " +
- "ExpectedPartitionKeys: 2 Reducers: 1 DistributePartitions: false")
+ val simpleAndExtendedKeyword =
+ "GroupPartitions JoinKeyPositions: [0] ExpectedPartitionKeys: 2 " +
+ "Reducers: [BucketReducer(2)] DistributePartitions: false"
+ val formattedKeyword =
+ "Arguments: JoinKeyPositions: [0], ExpectedPartitionKeys: 2, " +
+ "Reducers: [BucketReducer(2)], DistributePartitions: false"
+ checkKeywordsExistsInExplain(df, SimpleMode, simpleAndExtendedKeyword)
+ checkKeywordsExistsInExplain(df, ExtendedMode, simpleAndExtendedKeyword)
+ checkKeywordsExistsInExplain(df, FormattedMode, formattedKeyword)
}
}
}
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/connector/catalog/functions/transformFunctions.scala b/sql/core/src/test/scala/org/apache/spark/sql/connector/catalog/functions/transformFunctions.scala
index ed2f81d7e8d6..e7d05ab25e2d 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/connector/catalog/functions/transformFunctions.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/connector/catalog/functions/transformFunctions.scala
@@ -114,6 +114,7 @@ object BucketFunction extends ScalarFunction[Int] with ReducibleFunction[Int, In
case class BucketReducer(divisor: Int) extends Reducer[Int, Int] {
override def reduce(bucket: Int): Int = bucket % divisor
+ override def displayName(): String = toString
}
object UnboundStringSelfFunction extends UnboundFunction {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org