-
Notifications
You must be signed in to change notification settings - Fork 364
feat: support nested types as native shuffle hash partitioning keys #5567
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: main
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 |
|---|---|---|
|
|
@@ -51,7 +51,7 @@ import com.google.common.base.Objects | |
|
|
||
| import org.apache.comet.{CometConf, CometExplainInfo} | ||
| import org.apache.comet.CometConf.{COMET_SHUFFLE_ENABLED, COMET_SHUFFLE_MODE} | ||
| import org.apache.comet.CometSparkSessionExtensions.{cometCelebornShuffleFallbackReason, hasFallbackReason, isCometCelebornShuffleManagerEnabled, isCometShuffleManagerEnabled, withFallbackReasons} | ||
| import org.apache.comet.CometSparkSessionExtensions.{cometCelebornShuffleFallbackReason, hasFallbackReason, isCometCelebornShuffleManagerEnabled, isCometShuffleManagerEnabled, isSpark40Plus, withFallbackReasons} | ||
| import org.apache.comet.serde.{Compatible, OperatorOuterClass, QueryPlanSerde, SupportLevel, Unsupported} | ||
| import org.apache.comet.serde.operator.CometSink | ||
| import org.apache.comet.shims.{CometTypeShim, ShimCometShuffleExchangeExec} | ||
|
|
@@ -401,12 +401,21 @@ object CometShuffleExchangeExec | |
| private def nativeShuffleFailureReasons(s: ShuffleExchangeExec): Seq[String] = { | ||
| val conf = SQLConf.get | ||
|
|
||
| val nestedHashPartitioningEnabled = | ||
| CometConf.COMET_SHUFFLE_NATIVE_HASH_PARTITIONING_NESTED_ENABLED.get(conf) | ||
|
|
||
| /** | ||
| * Determine which data types are supported as partition columns in native shuffle. | ||
| * | ||
| * For HashPartitioning this defines the key that determines how data should be collocated for | ||
| * operations like `groupByKey`, `reduceByKey`, or `join`. Native code does not support | ||
| * hashing complex types, see hash_funcs/utils.rs | ||
| * operations like `groupByKey`, `reduceByKey`, or `join`. | ||
| * | ||
| * Nested types (struct/array/map) are supported when | ||
| * `spark.comet.shuffle.native.partitioning.hash.nested.enabled` is enabled: the native | ||
| * Murmur3 kernel in hash_funcs/utils.rs hashes them recursively. Nesting is checked | ||
| * recursively, so a leaf type that cannot be hashed natively -- a collated string, or an | ||
| * interval the hasher has no branch for -- disqualifies the whole key and the shuffle falls | ||
| * back to Spark. | ||
| */ | ||
| def supportedHashPartitioningDataType(dt: DataType): Boolean = dt match { | ||
| // Collated strings require collation-aware hashing; Comet only hashes raw bytes, | ||
|
|
@@ -424,6 +433,22 @@ object CometShuffleExchangeExec | |
| true | ||
| case dt if isTimeType(dt) => | ||
| true | ||
| case StructType(fields) if nestedHashPartitioningEnabled => | ||
| // `fields.nonEmpty` mirrors the guard on the data-column gate below. An empty struct is | ||
| // not reachable end-to-end anyway: Parquet cannot store an empty group, and an in-memory | ||
| // relation with one does not survive scan conversion. | ||
| fields.nonEmpty && fields.forall(f => supportedHashPartitioningDataType(f.dataType)) | ||
| case ArrayType(elementType, _) if nestedHashPartitioningEnabled => | ||
| supportedHashPartitioningDataType(elementType) | ||
| case MapType(keyType, valueType, _) if nestedHashPartitioningEnabled => | ||
| // Map entry order is not semantically meaningful, so two equal maps must hash alike. | ||
| // Spark 4.0+ normalizes a map shuffle key by wrapping it in `mapsort(...)`, which is | ||
| // gated separately by CometMapSort (scalar map keys only) and, when unsupported, fails | ||
| // the expression check below. Earlier Spark versions insert no such normalization, so | ||
| // Comet would hash physical entry order and could route equal maps differently. | ||
| isSpark40Plus && | ||
| supportedHashPartitioningDataType(keyType) && | ||
| supportedHashPartitioningDataType(valueType) | ||
|
Comment on lines
+443
to
+451
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. [P2] Rebase sliced map offsets before enabling map shuffle keys Could we fix
Please rebase the output offsets, or retain fallback for this case, and cover OFFSET followed by map repartition while asserting both operators run natively. This finding is source-derived. I have not executed the query. |
||
| case _ => | ||
| false | ||
| } | ||
|
|
||
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.
Do you have any numbers for this? What I am worried about is the shapes that miss the vectorized paths.
array<struct<...>>and a map nested inside a struct both fall throughhash_list_with_primitive_elements!intohash_list_array!, which slices a one element array and re-enterscreate_murmur3_hashesfor every element, plus a.columns().to_vec()per struct element on top of that. The four level key in the new tests pays that at every level.Since the config defaults to true we are opting everyone into this, and if it turns out slower than just letting Spark do the shuffle then the default is wrong.
struct<int, string>is not the interesting case here.