This choice is orthogonal to the processing model — you can mix and match (10:07, 34:11).
Pull (top-to-bottom)
- Start at the root, call
next(), which percolates down to the leaves; data is pulled up (30:45,31:18). - This is how most database systems are implemented (
31:18).
Pros (34:37)
- Easy output control — "stop calling next" is the whole implementation of
LIMIT. - No explicit "I have enough data" mechanism needed inside operators.
Cons (34:58)
- Overhead of repeated
next()calls; in C++ this means virtual function table lookups. - CPU cost is significant in a well-tuned in-memory system (irrelevant if you are already going to disk).
Push (bottom-to-top)
- Start at the leaves, run them, and push data up to parents (
31:35). - Requires a coordinator/scheduler above the plan that explicitly invokes each pipeline in dependency order and tells it where the intermediate results (e.g. the hash table) live (
33:12,33:47). - With fusion, the pipelines collapse into tight nested
forloops with nonext()indirection (32:37,33:00).
- Very tight control over where data is placed: HyPer and CedarDB keep data not just in CPU caches but in CPU registers, the fastest memory available.
- Because the plan is nested loops, systems can JIT-compile it and tune batch size to cache/register capacity.
- Rare in practice.
- Needs an explicit early-termination mechanism embedded in the nested loops to say "I have enough, stop".
Adopters and history
- DuckDB originally used pull and switched to push; a benefit they highlight is that a separate control-flow mechanism made engineering the system much easier (
35:49). - Snowflake uses push (
36:20). - Lineage: the MonetDB researchers who invented the vectorization model built VectorWise, acquired by Actian and eventually killed off; Marcin Zukowski then co-founded Snowflake, which is built on those ideas (
36:20,36:45).
graph LR
subgraph Pull
Root1[Root] -->|next| Mid1[Operator] -->|next| Leaf1[Scan]
Leaf1 -.tuple.-> Mid1 -.tuple.-> Root1
end
subgraph Push
Sched[Scheduler] -->|run pipeline| Leaf2[Scan]
Leaf2 -->|push| Mid2[Operator] -->|push| Root2[Output]
end