0%
Database Internals: Build the Engine
Storage Structures
Durability
Concurrency Control
Access Paths
Distribution
Execution Models, Volcano and Vectorized
A plan is a tree of operators, and the oldest and still most widespread way to run one is to let each operator ask its children for a row at a time, which is an arrangement whose elegance explains both its longevity and its limits.
What the model looks like
Every operator offers the same three operations, which are preparing itself, producing the next row, and finishing, and every operator is implemented against that interface.
Starting with the interface is right because the uniformity is the whole design. A scan, a filter, a join and an aggregate are interchangeable from their parent's point of view.
Why that uniformity is so valuable
Any operator can be a child of any other, so a new operator can be added without changing anything else and a plan can be assembled in any valid shape.
Explaining the benefit is necessary for fairness. This composability is why the model has survived decades and why it is the natural first implementation for any engine.
Why the model is demand driven
Nothing happens until the root is asked for a row, and that request travels down the tree until a scan produces something, so work is performed only as it is needed.
Describing the direction matters because it is the defining property. Rows are pulled from above rather than pushed from below, which has consequences throughout.
Why being demand driven stops work early
A query that stops after a handful of rows simply stops asking, and every operator below stops as soon as it is no longer called.
Naming the benefit is valuable because it is a real strength. Early termination falls out of the design with no special handling anywhere.
Why the memory profile is good
Only one row is in flight at a time through most of the tree, so the memory used is small and independent of the data volume.
Including the memory picture is necessary. For most operators the footprint is a single row, which is why this model runs comfortably in very little memory.
Why the model needs no scheduling
Control passes from operator to operator by ordinary calls and returns, so there is no queue of pending work, no coordination between operators and nothing to schedule.
Noting the absence is worthwhile because it is part of why the model is so simple to implement. The call stack is the execution state, which means a plan needs no machinery beyond the operators themselves.
Why some operators break that property
An operator needing all of its input before producing anything, such as a sort or a hash table build, must hold that input and so breaks the single-row footprint.
Introducing the blocking operators is necessary because they shape everything. They are where memory is consumed and where the pipeline stops being a pipeline.
Why the tree is walked only once
The shape of the plan is fixed before execution begins, so the operators are connected once and the rows then flow through a structure nobody has to reconsider.
Noting this is useful because it separates planning from running. Every decision about shape was made earlier, and execution only carries rows through the result.
What a pipeline is in this model
A run of consecutive operators that each pass a row straight through, so a row produced at the bottom reaches the top without being stored anywhere.
Defining the pipeline precisely is useful because it is the unit of execution. A plan is a sequence of pipelines separated by the operators that must hold their input.
Why pipelines matter for reading plans
The number of pipeline boundaries tells you how many times the data must be fully materialised, which is a better guide to cost than counting operators.
Making this practical is worthwhile. Two plans with the same operators arranged differently can have very different numbers of boundaries, and the boundaries are what cost.
What a filter looks like in this model
It asks its child for a row, tests the condition, returns the row if it passes and otherwise asks again, looping until a row passes or the child is exhausted.
Walking one operator through concretely is useful because it exposes the cost. A filter rejecting most rows performs many child calls per row it returns.
What a nested loop join looks like
It holds the current outer row, asks the inner side repeatedly, and when the inner side is exhausted asks the outer side for the next row and restarts the inner one.
Covering the join shows where the model gets awkward. Restarting a child is an extra operation beyond the basic three, and it constrains what can sit on the inner side.
What an aggregate looks like
It calls its child until exhaustion, accumulating as it goes, and then produces its results, which makes it a blocking operator by nature.
Including the aggregate completes the picture. It is the clearest example of an operator that cannot be part of a pipeline passing through it.
Why expression evaluation is interpreted
A condition or a computed column is represented as a small tree that is walked for every row, deciding at each node what operation to perform.
Introducing the interpretation is essential because it is the larger half of the overhead. The structure of the expression is examined again for every single row.
Why the same decisions are made repeatedly
Nothing about the expression changes between rows, yet the walk re-examines its shape, re-resolves its types and re-dispatches its operations every time.
Stressing the repetition is the bridge to the next section. The work is the same for every row, and it is performed per row anyway.
Why the model hides this cost in transactional work
A query touching a handful of rows performs a handful of these walks, so the per-row overhead is invisible next to the cost of locating the rows at all.
Explaining why nobody noticed for decades is important. The overhead is real and irrelevant at small scale, which is exactly the scale the model was designed for.
Why it stops being hidden
A query touching millions of rows performs millions of walks and millions of dispatches, and that becomes the dominant cost rather than a rounding error.
Naming the transition is the core argument of the lesson. Nothing changed about the model, and the workload changed around it.
Why the model is still the right choice for many engines
Its composability, its tiny memory footprint and its natural early termination are exactly what a transactional engine needs, and the overhead is immaterial there.
Being clear about this is important for balance. The alternatives in this lesson are improvements for one workload and not for all of them.
Why understanding the model is unavoidable
Every plan in every engine is still described as a tree of operators with this interface in mind, so reading a plan means reading this model regardless of how it executes.
Justifying the attention is worthwhile. The vocabulary of plans comes from here even in engines that abandoned the implementation long ago.
What to take from the iterator model
Three things. Every operator offers the same three operations, which makes any tree shape assemblable and is why the model has lasted. It is demand driven, so work happens only as rows are requested, which gives early termination and a single-row memory footprint for free, broken only by operators that must hold their whole input. And its per-row overhead, from dispatching through the interface and from re-interpreting every expression for every row, is immaterial on a handful of rows and dominant on millions.
Nothing went wrong with the iterator model. It was designed for queries touching a handful of rows, where the per-row overhead of a dispatch and an expression walk is invisible beside the cost of locating those rows at all. The workload changed around it: a query touching millions of rows performs millions of dispatches and millions of expression walks, and those become the dominant cost. The model is still the right choice for transactional work, which is why engines doing both often keep it.