You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Potential revamp of broadcast compression policy #2216
When the blocks creation phase initiated by spark here
It doesn't cause statistically significant improvement on the standard benchmarks (typically broadcasts are just tiny parts of the whole plan in terms of data movements), but it might hurt workloads where 100MB-1GB data chunks are explicitly targeted for being broadcast via hint or configuration.
Describe the potential solution
So, the point is checking two assumptions:
The query performance is notably regressed on queries where larger data portions are broadcast.
The double compression brings no benefits in terms of inter-node traffic volume.
If both are true, maybe it is worth turning off/making configurable broadcast compression on the comet side.
Isn't it the case that either the Spark Broadcast Exchange or the Comet Boadcast exchange will be used. In which case the compression will be either the Spark code path or the Comet code path?
@parthchandraThis is initiated for both Comet and Spark broadcast exchanges. Basically, it is a vanilla spark broadcast protocol which is used by Comet Boadcast exchange too. In a nutshell, comet specifies what should be broadcasted (compressed arrow batches) but it doesn't change how it should be broadcasted compare to Spark.
Setup. A throwaway Scala probe on local-cluster[2,4,12288] (two executor JVMs with 4 cores each), Spark 4.1.3, JavaSerializer and AQE off. The query joins a 4M-row fact table to a dimension with a BROADCAST hint. Each row of
the table is the median of 4 runs. "On" is Spark's default spark.broadcast.compress=true, and "off" is false, which
leaves only Comet's own compression.
Build side (size after Comet's compression)
Codec
Bytes each executor fetches, on vs off
Time to broadcast, on → off
Executor read, on → off
Wall, on → off
12M rows: sequential key, random long, random string (444 MiB)
lz4
401.5 vs 445.9 MiB (−10%)
376 → 139 ms
410 → 331 ms
2.87 → 2.10 s
same rows (273 MiB)
zstd level 1
247.5 vs 274.7 MiB (−10%)
146 → 96 ms
236 → 206 ms
1.57 → 1.53 s
same rows (284 MiB)
zstd level 5
262.8 vs 285.6 MiB (−8%)
923 → 87 ms
237 ms both
4.54 → 3.72 s
TPC-H-like customer, 3M rows (298 MiB)
lz4
271.9 vs 299.2 MiB (−9%)
263 → 96 ms
289 → 220 ms
2.02 → 1.76 s
the 12M rows in random order (456 MiB)
lz4
457.8 vs 458.2 MiB (−0.1%)
361 → 139 ms
644 → 498 ms
2.59 → 2.11 s
"Executor read" is how long an executor takes to get the value of a fresh broadcast of the same bytes: fetching,
decompressing and deserializing. Running only the torrent layer on the driver over the same bytes, for lz4, snappy
and zstd levels 1, 3 and 5, gives the same picture. The second pass saves 6 to 11% with every codec when the build
side has a sequential column, and 0% (lz4, snappy) to 6% (zstd) when it does not.
Conclusions
The regression is real. The second pass runs on the driver before the join can start. It costs about 50 ms per
100 MiB with lz4 and about 300 ms per 100 MiB with zstd at level 5, plus up to 150 ms on each executor with lz4.
The second pass is not useless. It shrinks broadcasts with sorted or sequential columns by about 10%.
Since only the first assumption holds, I don't think a Comet change is worth it. The natural one is to write the
coalesced broadcast uncompressed and let the torrent layer compress it once. From the same probe, I estimate that saves
0.2 to 0.25 s of driver time per 300 to 460 MiB broadcast with lz4 and at most about 0.6 s with zstd level 5. In
exchange, each executor would keep the uncompressed Arrow IPC bytes on heap, 1.1 to 3 times as much as now depending on
the data and codec. Users who want to skip the second pass can already set spark.broadcast.compress=false. The
caveat is that it also stops compressing Spark's own broadcasts. #7492 documents this in the tuning guide and closes
this issue.
Activity
parthchandra commentedon Sep 1, 2025
Isn't it the case that either the Spark Broadcast Exchange or the Comet Boadcast exchange will be used. In which case the compression will be either the Spark code path or the Comet code path?
akupchinskiy commentedon Sep 1, 2025
@parthchandra This is initiated for both Comet and Spark broadcast exchanges. Basically, it is a vanilla spark broadcast protocol which is used by Comet Boadcast exchange too. In a nutshell, comet specifies what should be broadcasted (compressed arrow batches) but it doesn't change how it should be broadcasted compare to Spark.
parthchandra commentedon Sep 2, 2025
I see. Thanks for clarifying!
comphead commentedon Nov 4, 2026
I measured both assumptions on
main(7bf572efb7).Setup. A throwaway Scala probe on
local-cluster[2,4,12288](two executor JVMs with 4 cores each), Spark 4.1.3,JavaSerializerand AQE off. The query joins a 4M-row fact table to a dimension with aBROADCASThint. Each row ofthe table is the median of 4 runs. "On" is Spark's default
spark.broadcast.compress=true, and "off" isfalse, whichleaves only Comet's own compression.
lz4zstdlevel 1zstdlevel 5lz4lz4"Executor read" is how long an executor takes to get the value of a fresh broadcast of the same bytes: fetching,
decompressing and deserializing. Running only the torrent layer on the driver over the same bytes, for
lz4,snappyand
zstdlevels 1, 3 and 5, gives the same picture. The second pass saves 6 to 11% with every codec when the buildside has a sequential column, and 0% (
lz4,snappy) to 6% (zstd) when it does not.Conclusions
100 MiB with
lz4and about 300 ms per 100 MiB withzstdat level 5, plus up to 150 ms on each executor withlz4.Since only the first assumption holds, I don't think a Comet change is worth it. The natural one is to write the
coalesced broadcast uncompressed and let the torrent layer compress it once. From the same probe, I estimate that saves
0.2 to 0.25 s of driver time per 300 to 460 MiB broadcast with
lz4and at most about 0.6 s withzstdlevel 5. Inexchange, each executor would keep the uncompressed Arrow IPC bytes on heap, 1.1 to 3 times as much as now depending on
the data and codec. Users who want to skip the second pass can already set
spark.broadcast.compress=false. Thecaveat is that it also stops compressing Spark's own broadcasts. #7492 documents this in the tuning guide and closes
this issue.