Skip to content

Potential revamp of broadcast compression policy #2216

Closed

Description

@akupchinskiy

What is the problem the feature request solves?

Currently, broadcast batches are compressed twice by the same codec:

  1. During rdd formation on the comet side here
  2. 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:

  1. The query performance is notably regressed on queries where larger data portions are broadcast.
  2. 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.

Additional context

No response

Activity

  1. parthchandra commented on Sep 1, 2025

    @parthchandra
    Contributor

    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?

  2. akupchinskiy commented on Sep 1, 2025

    @akupchinskiy
    ContributorAuthor

    @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.

  3. parthchandra commented on Sep 2, 2025

    @parthchandra
    Contributor

    I see. Thanks for clarifying!

  4. self-assigned this
    on Nov 4, 2026
  5. comphead commented on Nov 4, 2026

    @comphead
    Contributor

    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,
    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

    1. 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.
    2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions