Skip to content

perf: default the shuffle compression codec to zstd - #2805

Merged
andygrove merged 6 commits into
apache:mainfrom
andygrove:perf/zstd-shuffle-default
Nov 8, 2026
Merged

andygrove merged 6 commits into
apache:mainfrom
andygrove:perf/zstd-shuffle-default

Conversation

@andygrove

@andygrove andygrove commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Stacked on #2802. Review only the last two commits, perf: default the shuffle compression codec to zstd and perf: use zstd level 1 for shuffle compression.

Which issue does this PR close?

No issue. Split out of #2792. Follow-up for string-heavy shuffles: #2804.

Rationale for this change

Shuffles are often bound by how many bytes they move: across the network between executors, or through local disk and the page cache when they spill. For the columns TPC-H q5 shuffles from lineitem (l_orderkey, l_suppkey, l_extendedprice, l_discount), Arrow IPC with LZ4 stores about 16 bytes per row, and with zstd about 8.2. For reference, Spark + Comet's shuffle with its default codec (also LZ4) stores the same rows at about 14.8, so this is a codec comparison rather than a system one. zstd costs a little more CPU to compress, but that is small next to the bytes it saves.

Measured with the other changes in #2792 applied, LZ4 against zstd, one iteration each:

TPC-H LZ4 (s) zstd (s)
SF1000, two nodes, 2 executors × 16 cores, 256 partitions 1702 1318
SF100, one machine, 2 executors × 8 cores, 16 partitions 138.8 132.3

At SF1000 the biggest changes are q5 274 to 170 s, q3 113 to 76 s, q9 156 to 117 s, q12 50 to 35 s, q18 183 to 145 s and q21 199 to 161 s. No query got slower beyond noise: q11 went from 5.4 to 5.7 s. zstd is faster even on one machine, where the shuffle never crosses a network, because smaller files mean less disk and page-cache traffic.

#2802 is a prerequisite. Without it, the sort shuffle spill manager keeps one writer open per spilled output partition, and with zstd each open writer holds its own compression context outside the memory pool.

What changes are included in this PR?

ballista.shuffle.compression.codec defaults to zstd instead of lz4, with a description saying why, and configs.md is regenerated.

zstd is written at level 1 (SHUFFLE_ZSTD_COMPRESSION_LEVEL) instead of arrow's default of 3, matching Comet's shuffle default. Writing 60M TPC-H lineitem rows into 200 sort shuffle partitions with shuffle_bench:

Codec Output Write time
LZ4 4.29 GB 25.7 s
zstd level 1 2.55 GB 26.3-26.9 s
zstd level 3 2.50 GB 28.1-29.4 s

Level 3 saves under 2% more bytes for 7-10% more write time. The pipelined Flight serve path now builds its write options through the same helper, so it uses the same level. The TPC-H timings above were measured at level 3. Readers take the codec from the IPC stream metadata, so mixed old and new files read fine, and lz4 and none remain available.

The Flight serve path (ballista.shuffle.remote_read_prefer_flight, off by default) still re-encodes with LZ4 and is unchanged here.

Are there any user-facing changes?

Yes. Shuffle files and block-transfer remote reads use zstd unless ballista.shuffle.compression.codec is set. Set it to lz4 to keep the previous behavior.

sim added 2 commits November 7, 2026 14:35
The existing multi-input test never spilled, so nothing checked that each
output partition's range stitches every input's spill and in-memory bytes
together in order. Run it again with a tiny buffer budget so every input
spills several times and keeps an in-memory remainder.
The spill manager kept one IPC writer open per spilled output partition
until the task finished, each holding a file descriptor and its own
compression context outside the memory pool. With hundreds of output
partitions that is hundreds of open files per input partition and, with
zstd, hundreds of megabytes of compressor state. Write each spill event as
one complete stream appended to the partition's file and close it; readers
already cross concatenated streams.

@milenkovicm milenkovicm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @andygrove

Comment thread ballista/core/src/config.rs Outdated
Comment on lines +563 to +566
"Compression codec used for shuffle data written to disk and \
sent between executors. Possible values: none, lz4, zstd. \
zstd writes shuffle files about half the size of lz4 for little \
extra CPU, which matters most when shuffles cross the network".to_string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comet's shuffle defaults to lz4. Was the ~14.8 bytes/row figure Comet with lz4? If so, this compares codecs rather than systems; Comet with zstd would be like-for-like.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was Comet with its default lz4, so it was comparing codecs and not systems. I reworded the description to say so.

extra CPU, which matters most when shuffles cross the network".to_string(),
DataType::Utf8,
Some("lz4".to_string()),
Some("zstd".to_string()),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets arrow's default zstd level, 3. Comet uses level 1 for shuffle. Could we set the level explicitly (try_with_compression_level) and compare 1 vs 3?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I compared them with shuffle_bench on 60M lineitem rows into 200 partitions: level 1 gave 2.55 GB, level 3 gave 2.50 GB, and level 1 wrote 7-10% faster. So level 1 keeps almost all of the size win. 4980ebb sets level 1 explicitly for all shuffle writes, including the pipelined Flight path, and I added the numbers to the description.

sim added 3 commits November 8, 2026 02:28
Address review feedback: spill and the in-memory encode now call one
write_ipc_stream helper, SpillManager builds its IpcWriteOptions once,
derives spill paths from partition_counters instead of a duplicate map,
and the single-batch spill() wrapper is removed (tests call
spill_batches).
zstd writes shuffle data at roughly half the size of lz4 (about 8 bytes
per row against 16 for TPC-H lineitem's shuffled columns) for little
extra CPU. Shuffles that cross the network get correspondingly faster,
and smaller files also cut local disk and page-cache traffic. lz4 stays
available through ballista.shuffle.compression.codec.
Arrow defaults zstd to level 3. Level 1 keeps nearly all of the size
reduction over lz4 for noticeably less CPU: writing 60M TPC-H lineitem
rows into 200 sort shuffle partitions produced 2.55 GB at level 1 and
2.50 GB at level 3 (lz4: 4.29 GB), with level 1 writing 7-10% faster.
The pipelined Flight serve path now builds its options through
create_write_options so it uses the same level.
@andygrove
andygrove force-pushed the perf/zstd-shuffle-default branch from db6b52b to 4980ebb Compare November 8, 2026 09:51

@NoahKusaba NoahKusaba left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving!
I do wonder why comet is still using lz4 as default though.

@andygrove

Copy link
Copy Markdown
Member Author

Approving! I do wonder why comet is still using lz4 as default though.

Yes, I'm going to look into that tomorrow.

…ault

# Conflicts:
#	ballista/core/src/execution_plans/sort_shuffle/spill.rs
@andygrove
andygrove added this pull request to the merge queue Nov 8, 2026
Merged via the queue into apache:main with commit fd8b371 Nov 8, 2026
24 checks passed
@andygrove
andygrove deleted the perf/zstd-shuffle-default branch November 8, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants