This document describes Comet's native shuffle implementation (CometNativeShuffle), which performs shuffle operations entirely in Rust code for maximum performance. For the JVM-based alternative, see JVM Shuffle.
Native shuffle takes columnar input directly from Comet native operators and performs partitioning, encoding, and writing in native Rust code. This avoids the columnar-to-row-to-columnar conversion overhead that JVM shuffle incurs.
Comet Native (columnar) → Native Shuffle → Arrow IPC → columnar
Compare this to JVM shuffle's data path:
Comet Native (columnar) → ColumnarToRowExec → rows → JVM Shuffle → Arrow IPC → columnar
Native shuffle (CometExchange) is selected when all of the following conditions are met:
Shuffle mode allows native: spark.comet.shuffle.mode is native or auto.
Child plan is a Comet native operator: The child must be a CometPlan that produces columnar output. Row-based Spark operators require JVM shuffle.
Supported partitioning type: Native shuffle supports:
HashPartitioningRangePartitioningSinglePartitionRoundRobinPartitioningSupported partition key types: For HashPartitioning and RangePartitioning, partition keys must be primitive types. Complex types (struct, array, map) as partition keys require JVM shuffle. Note that complex types are fully supported as data columns in native shuffle.
┌─────────────────────────────────────────────────────────────────────────────┐
│ CometShuffleManager │
│ - Routes to CometNativeShuffleWriter for CometNativeShuffleHandle │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ CometNativeShuffleWriter │
│ - Builds protobuf operator plan: ShuffleWriter(child = childNativeOp) │
│ - Reads per-partition leaf iterators from CometNativeShuffleInputIterator │
│ - Drives one CometExecIterator per partition │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼ (JNI)
┌─────────────────────────────────────────────────────────────────────────────┐
│ ShuffleWriterExec (Rust) │
│ - DataFusion ExecutionPlan │
│ - Orchestrates partitioning and writing │
└─────────────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────────────┐ ┌───────────────────────────────────┐
│ MultiPartitionShuffleRepartitioner │ │ SinglePartitionShufflePartitioner │
│ (hash/range partitioning) │ │ (single partition case) │
└───────────────────────────────────┘ └───────────────────────────────────┘
│
▼
┌───────────────────────────────────┐
│ ShuffleBlockWriter │
│ (Arrow IPC + compression) │
└───────────────────────────────────┘
│
▼
┌─────────────────┐
│ Data + Index │
│ Files │
└─────────────────┘
| Class | Location | Description |
|---|---|---|
CometShuffleExchangeExec | .../shuffle/CometShuffleExchangeExec.scala | Physical plan node. Validates types and partitioning, creates CometShuffleDependency. |
CometNativeShuffleWriter | .../shuffle/CometNativeShuffleWriter.scala | Implements ShuffleWriter. Builds the unified ShuffleWriter(child = childNativeOp) plan and runs it in one CometExecIterator per partition. |
CometShuffleDependency | .../shuffle/CometShuffleDependency.scala | Extends ShuffleDependency. Holds shuffle type, schema, range partition bounds, and (native shuffle only) a NativeShuffleSpec. |
CometNativeShuffleInputRDD | .../shuffle/CometNativeShuffleInputRDD.scala | Thin scheduling-anchor RDD on the native-shuffle path. compute returns a CometNativeShuffleInputIterator carrying per-partition leaf iterators. |
CometBlockStoreShuffleReader | .../shuffle/CometBlockStoreShuffleReader.scala | Reads shuffle blocks via ShuffleBlockFetcherIterator. Decodes Arrow IPC to ColumnarBatch. |
NativeBatchDecoderIterator | .../shuffle/NativeBatchDecoderIterator.scala | Reads compressed Arrow IPC from input stream. Calls native decode via JNI. |
| File | Location | Description |
|---|---|---|
shuffle_writer.rs | native/core/src/execution/shuffle/ | ShuffleWriterExec plan and partitioners. Main shuffle logic. |
codec.rs | native/core/src/execution/shuffle/ | ShuffleBlockWriter for Arrow IPC encoding with compression. Also handles decoding. |
comet_partitioning.rs | native/core/src/execution/shuffle/ | CometPartitioning enum defining partition schemes (Hash, Range, Single). |
Plan construction: CometNativeShuffleWriter builds a protobuf operator tree with a ShuffleWriter operator at the root and childNativeOp as its child. childNativeOp takes one of two shapes:
nativeOp directly, when CometShuffleExchangeExec's child is a CometNativeExec subtree. The upstream operators run inside the same CometExecIterator as the writer, with no JVM-to-native batch boundary between them.Scan("ShuffleWriterInput") placeholder, when the dep was built via the convenience prepareShuffleDependency(rdd, ...) overload (used by CometCollectLimitExec and CometTakeOrderedAndProjectExec, or when the exchange's child is a non-native CometPlan such as CometSparkToColumnarExec). Native code reads ColumnarBatches from the JVM input iterator via Arrow C Stream Interface.Native execution: A single CometExecIterator per partition runs the unified plan.
Partitioning: ShuffleWriterExec receives batches and routes to the appropriate partitioner:
MultiPartitionShuffleRepartitioner: For hash/range/round-robin partitioningSinglePartitionShufflePartitioner: For single partition (simpler path)Buffering and spilling: The partitioner buffers rows per partition. When memory pressure exceeds the threshold, partitions spill to temporary files.
Encoding: ShuffleBlockWriter encodes each partition's data as compressed Arrow IPC:
Output files: Two files are produced:
Commit: Back in JVM, CometNativeShuffleWriter reads the index file to get partition lengths and commits via Spark's IndexShuffleBlockResolver.
CometBlockStoreShuffleReader fetches shuffle blocks via ShuffleBlockFetcherIterator.
For each block, NativeBatchDecoderIterator:
Native.decodeShuffleBlock() via JNINative code decompresses and deserializes the Arrow IPC stream.
Arrow FFI transfers the RecordBatch to JVM as a ColumnarBatch.
Native shuffle implements Spark-compatible hash partitioning:
partition_id = hash % num_partitionsFor range partitioning:
RangePartitioner samples data and computes partition boundaries on the driver.partition_point) determines which partition each row belongs to.The simplest case: all rows go to partition 0. Uses SinglePartitionShufflePartitioner which simply concatenates batches to reach the configured batch size.
Comet implements round robin partitioning using hash-based assignment for determinism:
partition_id = hash % num_partitionsThis approach guarantees determinism across retries, which is critical for fault tolerance. However, unlike true round robin which cycles through partitions row-by-row, hash-based assignment only provides even distribution when the data has sufficient variation in the hashed columns. Data with low cardinality or identical values may result in skewed partition sizes.
Native shuffle uses DataFusion's memory management with spilling support:
spark.comet.shuffle.native.maxBufferBytes. That config defaults to 0, which disables the fixed limit and leaves memory pressure as the only trigger.The MultiPartitionShuffleRepartitioner manages:
PartitionBuffer: In-memory buffer for each partitionSpillFile: Temporary file for spilled dataMemoryConsumer traitNative shuffle supports multiple compression codecs configured via spark.comet.shuffle.compression.codec:
| Codec | Description |
|---|---|
zstd | Zstandard compression. Best ratio, configurable level. |
lz4 | LZ4 compression. Fast with good ratio. |
snappy | Snappy compression. Fastest, lower ratio. |
none | No compression. |
The compression codec is applied uniformly to all partitions. Each partition's data is independently compressed, allowing parallel decompression during reads.
| Config | Default | Description |
|---|---|---|
spark.comet.shuffle.enabled | true | Enable Comet shuffle |
spark.comet.shuffle.mode | auto | Shuffle mode: native, jvm, or auto |
spark.comet.shuffle.compression.codec | zstd | Compression codec |
spark.comet.shuffle.compression.zstd.level | 1 | Zstd compression level |
spark.comet.shuffle.native.writeBufferSize | 1MB | Write buffer size |
spark.comet.shuffle.jvm.batchSize | 8192 | Target rows per batch |
| Aspect | Native Shuffle | JVM Shuffle |
|---|---|---|
| Input format | Columnar (direct from Comet operators) | Row-based (via ColumnarToRowExec) |
| Partitioning logic | Rust implementation | Spark's partitioner |
| Supported schemes | Hash, Range, Single, RoundRobin | Hash, Range, Single, RoundRobin |
| Partition key types | Primitives only (Hash, Range) | Any type |
| Performance | Higher (no format conversion) | Lower (columnar→row→columnar) |
| Writer variants | Single path | Bypass (hash) and sort-based |
See JVM Shuffle for details on the JVM-based implementation.