14. Execution model
14.1 From text to rows
| Step | What happens |
|---|---|
| Prepare | $name bound as literals; let evaluated; let (query) read; on view replaced by the view’s query (or its cache collection); values […] written as constant collections |
| Parse | The lexer and the pipeline parser split stages and sub-pipelines |
| Plan | Each stage’s arguments are checked against the stage catalogue; expressions are parsed and checked (function names, argument counts); the plan is validated (placement, terminal stages) |
| Optimize | Filters move toward the source; constant conditions fold; sort … limit becomes Top-N; equality and prefix joins get an index |
| Authorize | Read access to every collection read, write access to every collection written |
| Execute | Streaming, governed blocking, or transactional executor (14.3) |
Planning errors are raised before any data is read. Plans are cached: a repeated query skips parsing and planning.
14.2 Filters move toward the source
The planner splits where a and b into where a | where b (same result, same errors, same order) and moves each
filter before the stages that leave its fields and the set of rows alone:
| A filter moves past | When |
|---|---|
derive, select, rename, drop, unwind | They do not write the fields it reads |
lookup | It does not fill one of its fields |
sort | Always |
group, pivot | It reads only their keys (read as optional: a key of a row without the field is null) |
window | It reads only partition fields |
union | Also copied at the end of the branch (not when it reads the branch’s alias) |
It stops at any other stage (limit, skip, first, distinct, fill…) and after another filter, whose order
it keeps. A guard then goes on alone: for each comparison of a field with a constant,
type(f?) != "string" or f? == "x", which cannot fail and drops only rows the filter would drop. The filter itself
stays where it stopped, so no row reaches it that did not before, and no error is lost. explain shows the result.
A filter written after a view therefore reaches the view’s source:
let $end = "2026-03" | on view ledger.statement | where entity == "E1"
reads the ledger with the guard on entity right after the view’s own date filter, instead of classifying every
entity first (10.2 s → 0.25 s on a ledger of 1.8 M rows).
14.3 Execution shapes
ogd never falls back to loading everything in memory. A read query fits one of these shapes, or is refused before
it runs.
1. Pure streaming. Any mix of where, select, derive, rename, drop, root, near, skip, limit,
first, single, unwind, optionally ending with count. Rows flow one by one; a row bound (limit, first)
stops the scan early.
2. A streaming prefix, then blocking stages. The blocking stages are sort, distinct, group, bucket,
pivot, window, fill, sample, lookup and union. Any number may follow one another, each followed by
row-local stages, and the query may end with count.
- The first blocking stage reads the streaming prefix;
group | sortandsort | distinctrun as one stage. - Each later blocking stage takes the rows of the stages before it once they are all produced.
- A leading
skip k/limit n/firstafter a blocking stage bounds it, even acrossselect,derive,rename,drop,root; a stage that can drop rows (where,near) stops that:sort k | skip 10 | limit 5is a bounded Top-15. sort,group,pivot,windowandfillspill sorted runs to disk when their state outgrows the memory they can get.
Anything else is refused:
query plan contains a blocking stage sequence without a bounded executor;
materialized fallback is forbidden by the memory contract
3. Transactional. Writes (set, delete, load, insert, into) and system collections (_…) take the
transactional path, which these shapes do not restrict.
14.4 Memory
Every query is admitted with a small reservation and reserves memory as its blocking stages grow; all queries share
the query budget of the memory profile. OGD_MEMORY_LIMIT sets the profile; on _memory and on _query_memory
show it live.
| Class | Holds |
|---|---|
page_cache | Storage pages |
query | Working sets of running queries |
import | Imports in progress |
network | Buffers of connections |
indexing | Index builds |
planner | The plan cache |
results | The result cache |
A request waits for a query slot when all are busy (query wait, 60 s by default).
14.5 Joins at scale
- An equality or prefix
lookupindexes the joined source once per query. - A deterministic sub-pipeline reading only known outer fields is run once per distinct value of those fields.
- The index and kept results live within the query working set; past it, rows are joined by scan.
14.6 Grouping at scale
groupreads the storage’s per-field kinds to decide the compact form’s roles before the scan.- A one-key
groupwhose only stage before is aderiveof+ - * /over number fields is computed on each storage block’s typed columns (37 → 22 ms on 2 M rows). group | limit Nkeeps only the N smallest live keys.
14.7 Tips
- Filter early and on fields that exist everywhere.
selectwhat you need before a blocking stage: smaller rows, smaller spills.- Prefer
sort … | limit Ntosortalone. - Materialize costly shapes read by many queries (chapter 12).
- Read the plan:
explain ….