Joins: Beginner
Why a Join Moves Data
You can now explain why a join forces both tables through a shuffle to co-locate matching keys.
The bill is on the inputs
Broadcasting the Small Side
You can now describe how a broadcast join ships the small table everywhere and deletes the shuffle.
The map-side join
Which Side Is Small Enough
You can now judge from a table's nature and size whether it is a broadcast candidate.
| Table | What it is | Broadcast it? |
|---|---|---|
| country_codes, 200 rows | A bounded lookup | Always |
| products, 2M rows | A dimension, tens of megabytes | Usually |
| users, 80M rows | A dimension that grew into a fact | Rarely; measure first |
| orders, 400M rows and growing | A fact table | Never |
Small today is not small forever
Join Type vs Join Strategy
You can now separate the join type you wrote from the strategy Spark chose, and debug each on its own axis.
- inner, left, right, full, semi, anti
- Decides which rows survive
- Written by you, in the code
- Changes the answer
- Broadcast the small side, or shuffle both
- Decides how rows physically meet
- Chosen by the engine, from sizes
- Changes the runtime and the bill
Where type does constrain strategy
A left outer join attaches a small regions lookup to a huge events table and runs slowly. Which change speeds it up without changing the result?
The Duplicate-Key Blowup
You can now predict and prevent the row multiplication a duplicated join key causes.
| product_id | Rows in orders | Rows in products | Rows out |
|---|---|---|---|
| 4017 | 3 | 1 | 3 |
| 4018 | 5 | 1 | 5 |
| 4019 | 4 | 3 (duplicated!) | 12 |
| All keys | 12 | 5 | 20 |
Guard the grain
- Estimate both sides of every join before you write it; the small side decides the strategy.
- Ask of every join whether one side is a bounded lookup or dimension table.
- Verify the join key's uniqueness on the one-per-key side before trusting the grain.
- Filter both tables before the join so whatever movement happens moves less.
- Don't change the join type to fix performance; type is logic and changes the answer.
- Don't broadcast a table that grows with the business, no matter how small it is today.
- Don't patch a duplicate-key blowup with dropDuplicates after the join; fix the grain.
- Don't judge a join by its output size; the shuffle bill is set by the inputs.
> A nightly job that joins a 600 million row orders table to a small store locations table has started taking 4 hours, and finance also reports that this month's revenue dashboard is mysteriously 15 percent high.
A join is a meeting. Someone has to pay for the travel.
- Category
- Spark
- Difficulty
- beginner
- Duration
- 13 minutes
- Challenges
- 15 hands-on challenges
Topics covered: Why a Join Moves Data, Broadcasting the Small Side, Which Side Is Small Enough, Join Type vs Join Strategy, The Duplicate-Key Blowup
Lesson Sections
- Why a Join Moves Data (concepts: paDistributedPrimitives)
Start with what a join physically has to do. To match an order to its product, Spark must compare two rows, one from orders and one from products, sharing a product_id. But those rows live wherever the data happened to be read: the order sits in a partition on one machine, the product in a partition on another. No amount of cleverness lets two machines compare rows without at least one of them moving. A join is therefore a co-location problem before it is a matching problem: every pair of rows t
- Broadcasting the Small Side (concepts: paBroadcastJoin)
Look at the orders-to-products join again with fresh eyes. Orders is 400 million rows; products is 2 million rows, maybe 80 megabytes on disk. The default plan shuffles both, which means 400 million order rows cross the network to meet a table that would fit in the memory of a phone. That is backwards. If products is small enough to hold in memory, the cheaper move is obvious: leave the giant table exactly where it is, and send a complete copy of the small table to every executor instead. That i
- Which Side Is Small Enough (concepts: paBroadcastJoin)
The broadcast trade is network for memory: you avoid shuffling the big table by making every executor hold the entire small one. So the operative question about the small side is not how many rows it has but whether a full copy sits comfortably in one executor's memory next to everything else that executor is doing: shuffle buffers, cached data, the tasks themselves. If the answer is comfortably yes, on every executor, with room to spare after the hash-map form inflates it, broadcast is on the t
- Join Type vs Join Strategy (concepts: paDistributedPrimitives)
Two vocabularies collide in every conversation about joins, and keeping them apart is a mark of someone who understands the system. The first vocabulary is the join type: inner, left outer, right outer, full outer, semi, anti. That is logic. It answers one question: which rows appear in the result. The second vocabulary is the join strategy: broadcast the small side, or shuffle both sides. That is physics. It answers a different question: how do matching rows physically meet. The type is in your
- The Duplicate-Key Blowup (concepts: paDistributedPrimitives)
There is one way a join hurts you that has nothing to do with strategy, and it belongs in the beginner tier because it produces wrong bills and wrong dashboards, not just slow jobs. A join matches every qualifying pair. If a key appears m times on the left and n times on the right, the result contains m times n rows for that key. One-to-one and one-to-many joins behave the way intuition expects. Many-to-many joins multiply, and they usually multiply by accident. Where do the duplicates come from