Unit 6 · Databases · Lesson 6 of 10
Query execution: joins, aggregation, vectorization
Step 1: Learn the ideas
- Access methods: sequential, index and bitmap scans
- Nested-loop joins: simple, block and index
- Sort-merge join and external merge sort
- Hash join: build and probe, Grace and hybrid hash join, spilling
- Hash-based and sort-based aggregation; DISTINCT and GROUP BY
- Processing models: iterator (Volcano), materialization, vectorized
- Pipelines and pipeline breakers
- Query compilation (data-centric code generation)
- Intra-query parallelism, exchange operators and morsels
- Skew in partitioned joins and aggregation
Why it matters
The nodes in an EXPLAIN plan are these algorithms. Knowing their costs lets you explain why a query spilled to disk, why a nested loop was disastrous or why a different join order changed memory use. Research and performance roles go further, into how engines minimize per-tuple overhead on modern CPUs.
Core ideas
Joins. For R ⋈ S with M and N pages and B buffer pages:
- Block nested loop reads R once and S once per block of R: about M + ⌈M/(B−2)⌉·N I/Os, fine when one input is tiny.
- Index nested loop probes an index on S for each R tuple; good when R is small after filtering and the index is selective.
- Sort-merge sorts both inputs (unless already sorted) and merges them; attractive when inputs arrive sorted or the output must be sorted.
- Hash join builds a hash table on the smaller input and probes it with the other. If the build side does not fit, Grace hash join first partitions both inputs with the same hash function, so matching tuples land in matching partitions, for about 3(M + N) I/Os. Hashing needs equality predicates; other join conditions need nested loops or sorting.
Sorting and aggregation. External merge sort writes sorted runs of B pages, then merges B−1 runs at a time: about 1 + ⌈log_(B−1)⌈N/B⌉⌉ passes, each reading and writing N pages. Aggregation either sorts and scans groups or hashes on the group key; hashing is usually faster unless sorted output is needed anyway.
Processing models. In the iterator model every operator offers next() and pulls one tuple at a time: simple and composable, but every tuple pays function-call and interpretation overhead. The materialization model passes whole results, which suits OLTP queries with small outputs. Vectorized engines pass batches of hundreds to a few thousand values per column, amortizing overhead and enabling tight loops and SIMD. Compiling engines generate code per pipeline, fusing operators so a tuple stays in registers from the scan up to the next pipeline breaker, an operator such as a hash build, sort or full aggregation that must consume all its input before producing output.
index NLJ: for r in R: for s in index_lookup(S, r.k): emit(r, s)
hash join: build H on S.k; for r in R: for s in H.get(r.k): emit(r, s)
Skew. If one key holds a large share of rows, one hash partition or one worker gets most of the work. Engines detect heavy hitters and process them separately, or broadcast the matching rows of the other input.
Common misconceptions
- "Hash join is always best." It needs an equality predicate and memory for the build side; a selective index or sorted inputs can make other joins cheaper.
- "Nested-loop joins are always bad." With a tiny outer input and an index on the inner input they are often cheapest; the danger is choosing one on an underestimate.
- "Vectorized and compiled execution are rival fads." Both remove interpretation overhead; a controlled comparison found compilation stronger on computation-heavy pipelines and vectorization better at hiding memory stalls, and some systems combine them.
- "Spilling only happens on huge tables." It happens whenever an operator exceeds its memory budget, often because of a misestimate.
How to approach it
Do the cost arithmetic for each join on one example until it is automatic. Then implement: BusTub's query execution project covers scans, aggregation, hash join, external sort and an optimizer rule. Finally, read the 15-721 notes on vectorized execution and compilation, and keep conclusions modest: the better model depends on the workload.
Suggested order
- 1Read CMU 15-445 Fall 2025 Lecture #11 notes (Sorting & Aggregations Algorithms) and compute pass counts for two example sizes.
- 2Read Lecture #12 notes (Joins Algorithms) and redo every cost formula on your own example.
- 3Read Lecture #13 notes (Query Execution I) for processing models and access methods, and Lecture #14 for parallel execution.
- 4Use Berkeley CS186 Note 9 (Iterators and Joins) for extra worked cost examples.
- 5Read the CMU 15-721 Spring 2024 notes on Vectorized Query Execution and Code Generation & Compilation.
- 6Read Kersten et al. (compiled versus vectorized) for a controlled comparison of the two models.
- 7Implement BusTub Project #3 (Query Execution), or a small executor in Python over CSV files with a hash join and an external sort.
Step 2: Check off what you can do
Tap each one you can genuinely do or explain. Each earns 10 XP.
Step 3: Go deeper
CMU 15-445/645 Intro to Database SystemsStart here
Carnegie Mellon University Database GroupSection: Fall 2025 Lecture #12: Joins Algorithms (notes)
Nested-loop, sort-merge and hash join algorithms with I/O cost formulas; Lectures #11 and #13 cover sorting, aggregation and processing models.
Open(opens in a new tab)Link responded, 5 Oct 2026Berkeley CS186 Database Systems course notes
UC Berkeley CS186Section: Note 9: Iterators and Joins
Worked I/O cost calculations for join algorithms and the iterator interface.
Open(opens in a new tab)Link responded, 5 Oct 2026CMU 15-721 Advanced Database Systems
Carnegie Mellon University Database GroupSection: Spring 2024 Lecture #06: Vectorized Query Execution (notes)
How vectorized engines are built; continue with Lecture #07 (Code Generation & Compilation) in the same offering.
Open(opens in a new tab)Link responded, 5 Oct 2026Everything You Always Wanted to Know About Compiled and Vectorized Queries But Were Afraid to Ask
PVLDB (VLDB Endowment)A controlled comparison of compiled and vectorized execution in one testbed, with the hardware effects behind each.
Open(opens in a new tab)Link responded, 5 Oct 2026Architecture of a Database SystemStart here
UC Berkeley (author-hosted PDF)Section: Chapter 4: Relational Query Processor
How the executor, access methods and optimizer fit together in real systems.
Open(opens in a new tab)Link responded, 5 Oct 2026
Step 4: Practice
Merge k Sorted Lists is the k-way merge at the heart of external sort; solve it first. Then implement the executors in BusTub Project #3 (or a small Python executor over CSV files) and test each operator on empty, skewed and larger-than-memory inputs. Finish with a cost drill: for three table-size and memory settings, compute each join’s cost and the external sort pass count, and check one case in PostgreSQL with EXPLAIN (ANALYZE, BUFFERS) after lowering work_mem until the sort or hash join spills.
- CMU 15-445 Fall 2025 Project #3: Query Execution (BusTub)Open(opens in a new tab)Not attemptedCMU 15-445
- Merge k Sorted ListsLeetCode(opens in a new tab)Not attemptedLeetCode
- CMU 15-445 BusTub projects (buffer pool, index, query execution, concurrency control)Open(opens in a new tab)Not attemptedCMU 15-445
Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).
Step 5: Test yourself
Answer each one out loud or on paper before you finish the lesson.
- Question 1How many passes does external merge sort need for 1,000,000 pages with 100 buffer pages?
- Question 2Why must Grace hash join partition both inputs with the same hash function?
- Question 3When is an index nested-loop join better than a hash join?
- Question 4What makes an operator a pipeline breaker?
- Question 5Why does the iterator model have high per-tuple overhead on modern CPUs?
- Question 6How would one very frequent join key affect a partitioned parallel hash join?
You’re done when you can
- Compute costs for four join algorithms on a given example without notes and pick the cheapest with justification.
- Mark the pipeline breakers in three EXPLAIN plans and state their memory needs.
- Implement a hash join and an external sort that pass tests on inputs larger than the memory budget.
- Explain the iterator, vectorized and compiled models and one measured tradeoff between the last two.
Finish the lesson
Unlocks next: Query optimization and cardinality estimation, OLTP vs OLAP, distributed joins and distributed transactions


