Broadcast, shuffle and runtime filters
When does a runtime filter earn its barrier?
Broadcast, shuffle, and the partitioning you already have
A shuffle join repartitions both inputs by the hash of the join key, then runs a local join on each partition. The network cost is roughly |R| + |S| (each row moves once). The payoff is that per-node build memory becomes build/P. Dividing the memory problem by the parallelism is the whole point.
A broadcast join copies the small side to all P nodes. The network cost is |S_small|·P. The large side never moves and, importantly, it keeps its partitioning. A later aggregation or join on the same key then also avoids a shuffle. Property preservation compounds down the plan; a local cost saving does not.
The network break-even is |S_small|·P < |R| + |S_small|. This reduces to: broadcast when the size ratio exceeds the parallelism (|R| / |S_small| > P roughly).
- the large input
- the small input (copied to every node)
- nodes
Data table
| Parallelism P | series | Bytes moved |
|---|---|---|
| 10 | broadcast | 4.66 GB |
| 10.8 | broadcast | 5.03 GB |
| 11.7 | broadcast | 5.44 GB |
| 12.6 | broadcast | 5.88 GB |
| 13.6 | broadcast | 6.35 GB |
| 14.7 | broadcast | 6.87 GB |
| 15.9 | broadcast | 7.42 GB |
| 17.2 | broadcast | 8.02 GB |
| 18.6 | broadcast | 8.67 GB |
| 20.1 | broadcast | 9.37 GB |
| 21.7 | broadcast | 10.1 GB |
| 23.5 | broadcast | 10.9 GB |
| 25.4 | broadcast | 11.8 GB |
| 27.5 | broadcast | 12.8 GB |
| 29.7 | broadcast | 13.8 GB |
| 32.1 | broadcast | 14.9 GB |
| 34.7 | broadcast | 16.1 GB |
| 37.5 | broadcast | 17.4 GB |
| 40.5 | broadcast | 18.9 GB |
| 43.8 | broadcast | 20.4 GB |
| 47.3 | broadcast | 22 GB |
| 51.1 | broadcast | 23.8 GB |
| 55.2 | broadcast | 25.7 GB |
| 59.7 | broadcast | 27.8 GB |
| 64.5 | broadcast | 30 GB |
| 69.7 | broadcast | 32.5 GB |
| 75.4 | broadcast | 35.1 GB |
| 81.5 | broadcast | 37.9 GB |
| 88 | broadcast | 41 GB |
| 95.1 | broadcast | 44.3 GB |
| 103 | broadcast | 47.9 GB |
| 111 | broadcast | 51.8 GB |
| 120 | broadcast | 55.9 GB |
| 130 | broadcast | 60.4 GB |
| 140 | broadcast | 65.3 GB |
| 152 | broadcast | 70.6 GB |
| 164 | broadcast | 76.3 GB |
| 177 | broadcast | 82.5 GB |
| 191 | broadcast | 89.1 GB |
| 207 | broadcast | 96.3 GB |
| 224 | broadcast | 104 GB |
| 242 | broadcast | 113 GB |
| 261 | broadcast | 122 GB |
| 282 | broadcast | 131 GB |
| 305 | broadcast | 142 GB |
| 330 | broadcast | 154 GB |
| 356 | broadcast | 166 GB |
| 385 | broadcast | 179 GB |
| 416 | broadcast | 194 GB |
| 450 | broadcast | 210 GB |
| 486 | broadcast | 226 GB |
| 526 | broadcast | 245 GB |
| 568 | broadcast | 264 GB |
| 614 | broadcast | 286 GB |
| 663 | broadcast | 309 GB |
| 717 | broadcast | 334 GB |
| 775 | broadcast | 361 GB |
| 838 | broadcast | 390 GB |
| 905 | broadcast | 422 GB |
| 978 | broadcast | 456 GB |
| 1.06K | broadcast | 492 GB |
| 1.14K | broadcast | 532 GB |
| 1.24K | broadcast | 575 GB |
| 1.33K | broadcast | 622 GB |
| 1.44K | broadcast | 672 GB |
| 1.56K | broadcast | 726 GB |
| 1.69K | broadcast | 785 GB |
| 1.82K | broadcast | 848 GB |
| 1.97K | broadcast | 917 GB |
| 2.13K | broadcast | 991 GB |
| 2.3K | broadcast | 1.05 TB |
| 2.49K | broadcast | 1.13 TB |
| 2.69K | broadcast | 1.22 TB |
| 2.9K | broadcast | 1.32 TB |
| 3.14K | broadcast | 1.43 TB |
| 3.39K | broadcast | 1.54 TB |
| 3.66K | broadcast | 1.67 TB |
| 3.96K | broadcast | 1.8 TB |
| 4.28K | broadcast | 1.95 TB |
| 4.63K | broadcast | 2.1 TB |
| 5K | broadcast | 2.27 TB |
| 10 | shuffle | 373 GB |
| 10.8 | shuffle | 373 GB |
| 11.7 | shuffle | 373 GB |
| 12.6 | shuffle | 373 GB |
| 13.6 | shuffle | 373 GB |
| 14.7 | shuffle | 373 GB |
| 15.9 | shuffle | 373 GB |
| 17.2 | shuffle | 373 GB |
| 18.6 | shuffle | 373 GB |
| 20.1 | shuffle | 373 GB |
| 21.7 | shuffle | 373 GB |
| 23.5 | shuffle | 373 GB |
| 25.4 | shuffle | 373 GB |
| 27.5 | shuffle | 373 GB |
| 29.7 | shuffle | 373 GB |
| 32.1 | shuffle | 373 GB |
| 34.7 | shuffle | 373 GB |
| 37.5 | shuffle | 373 GB |
| 40.5 | shuffle | 373 GB |
| 43.8 | shuffle | 373 GB |
| 47.3 | shuffle | 373 GB |
| 51.1 | shuffle | 373 GB |
| 55.2 | shuffle | 373 GB |
| 59.7 | shuffle | 373 GB |
| 64.5 | shuffle | 373 GB |
| 69.7 | shuffle | 373 GB |
| 75.4 | shuffle | 373 GB |
| 81.5 | shuffle | 373 GB |
| 88 | shuffle | 373 GB |
| 95.1 | shuffle | 373 GB |
| 103 | shuffle | 373 GB |
| 111 | shuffle | 373 GB |
| 120 | shuffle | 373 GB |
| 130 | shuffle | 373 GB |
| 140 | shuffle | 373 GB |
| 152 | shuffle | 373 GB |
| 164 | shuffle | 373 GB |
| 177 | shuffle | 373 GB |
| 191 | shuffle | 373 GB |
| 207 | shuffle | 373 GB |
| 224 | shuffle | 373 GB |
| 242 | shuffle | 373 GB |
| 261 | shuffle | 373 GB |
| 282 | shuffle | 373 GB |
| 305 | shuffle | 373 GB |
| 330 | shuffle | 373 GB |
| 356 | shuffle | 373 GB |
| 385 | shuffle | 373 GB |
| 416 | shuffle | 373 GB |
| 450 | shuffle | 373 GB |
| 486 | shuffle | 373 GB |
| 526 | shuffle | 373 GB |
| 568 | shuffle | 373 GB |
| 614 | shuffle | 373 GB |
| 663 | shuffle | 373 GB |
| 717 | shuffle | 373 GB |
| 775 | shuffle | 373 GB |
| 838 | shuffle | 373 GB |
| 905 | shuffle | 373 GB |
| 978 | shuffle | 373 GB |
| 1.06K | shuffle | 373 GB |
| 1.14K | shuffle | 373 GB |
| 1.24K | shuffle | 373 GB |
| 1.33K | shuffle | 373 GB |
| 1.44K | shuffle | 373 GB |
| 1.56K | shuffle | 373 GB |
| 1.69K | shuffle | 373 GB |
| 1.82K | shuffle | 373 GB |
| 1.97K | shuffle | 373 GB |
| 2.13K | shuffle | 373 GB |
| 2.3K | shuffle | 373 GB |
| 2.49K | shuffle | 373 GB |
| 2.69K | shuffle | 373 GB |
| 2.9K | shuffle | 373 GB |
| 3.14K | shuffle | 373 GB |
| 3.39K | shuffle | 373 GB |
| 3.66K | shuffle | 373 GB |
| 3.96K | shuffle | 373 GB |
| 4.28K | shuffle | 373 GB |
| 4.63K | shuffle | 373 GB |
| 5K | shuffle | 373 GB |
The network arithmetic permits far larger broadcasts than any engine allows, because two other costs bind first:
- The small side must be resident on every executor, so the memory used is |S_small|·P. The shuffle's cost was relocated into memory, not removed.
- The hash table is built P times instead of once, so the total build CPU is P× higher.
So the claim "broadcasting a small table is always free" is wrong. With P in the hundreds, a 500 MB broadcast is 100 GB of cluster memory and a hundred or more redundant builds. In engines that route the broadcast through a central driver process, it is also a serialization point that fails hard instead of slowly.
The third option costs nothing at run time: both tables pre-partitioned on the join key at write time (bucketing, co-partitioned tables, a shared DISTRIBUTED BY clause). The network cost is zero. This is a write-time investment being cashed in. Its price is paid where you do not see it:
- every other query is now partitioned on a key it does not want;
- the bucket counts on both sides must match or divide evenly;
- any rewrite of either table that changes the bucketing silently invalidates the plan.
It is the highest-leverage and the most brittle option in this article.
Sideways information passing: move the summary, not the rows
The idea: send a compact description of the build side's keys to the probe side's scan before the probe side moves any data, and discard non-matching rows at the source. In practice this is a runtime filter, usually a Bloom filter.
Its leverage is the ratio of the filter's size (about 10 bits per key) to the rows it keeps from moving (often hundreds of bytes each), and its cost is that the filter must exist before the probe side is scanned, a barrier between two pipelines that were independent. How big to make the filter, which kind to build (min/max, IN-list or Bloom) and when it earns its barrier are the planner's decision (Dynamic partition pruning and runtime filters). On the join side, what matters is what the filter does to correctness.
Why the approximation is safe here. A Bloom filter's false positives let a few non-matching rows through. But an exact join follows the filter, so those rows are removed later. False positives cost work, not correctness. This is the general safe pattern: an approximate pre-filter in front of an exact operator gives an exact answer at reduced cost. Compare approximate aggregation in Aggregation algorithms, where the approximation is in the answer itself. Same mathematics, very different risk.