In Build & run Clinker you ran --explain and saw a few lines of
output. Let’s actually read it. It’s the single best way to understand a pipeline before it
runs, and your first glimpse of an idea that shapes the whole engine: Clinker plans a job
before it runs it.
--explain takes the YAML all the way through compilation (parsing, validation,
typechecking the CXL, building the graph) and then prints the result instead of
executing it. Nothing is read from the source; nothing is written. Here’s the heart
of the output:
=== Resolved Outputs ===
'results' → ./output/customers.csv
=== Execution Plan ===
Mode: Streaming
Transforms: 2
Output projections: 1
DAG nodes: 4
arbitration: BackPressurePreferred -> Priority
Source DAG:
Tier 0: customers
Transform 'active_only':
Parallelism: Stateless
Transform 'final_flag':
Parallelism: Stateless
Read it top to bottom:
Resolved Outputs: where results will land (./output/customers.csv).
DAG nodes: 4, the source, two transforms, and the output, wired into a directed
acyclic graph.
Mode: Streaming, so records can flow straight through without the engine having to
collect them all first.
Source DAG / Tier 0: customers: the one source sits at tier 0,
the first (and here only) wave of sources to read.
Parallelism: Stateless (per transform): each row is independent, so these
transforms have no cross-record state.
arbitration: BackPressurePreferred -> Priority, the memory policy
the run would use if it came under pressure. You’re seeing the engine’s bounded-memory
machinery named, before a byte is allocated.
--explain even prints a Physical Properties section with each node’s predicted
peak memory (for this tiny job, predicted_peak=345B, the size of the input CSV).
That a plan exists as its own thing, printable, inspectable, produced before
execution, is not an accident. Compilation turns the YAML into a typed, validated
artifact, and only that artifact is handed to the runtime:
dag:ExecutionPlanDag,// the lowered execution graph
config:PipelineConfig,// the validated configuration
// …plus the validated compile outputs the runtime reads (typechecked CXL, bound schemas)
// ...
}
We won’t unpack CompiledPlan yet; that’s a Planning & Expressions
lesson. For now, just hold the shape of the idea: plan first, run second.--explain is
you stepping in between the two.
You can read the plan as given. Now turn it into a skill: change one thing, predict how
the plan shifts (or doesn’t), and verify. Each rung uses a real pipeline that ships in
examples/pipelines/.
You already did this above. Confirm you can answer, without re-running it, the three
checkpoint-grade questions about customer_etl’s plan: how many DAG nodes (4),
how many transforms (2), what mode (Streaming). If any of the three isn’t
instant yet, re-read the output once more before moving on; the next two rungs build on
reading this fluently.
Here’s the question that separates operating the flag from reading the engine: which
changes reshape the plan, and which don’t? Two modifications, opposite answers.
Modify A: override a variable’s value.customer_etl declares a variable,
gold_threshold (default 10000), and a channel can override it per tenant. There is
no --var CLI flag; the override is a .channel.yaml overlay you pass with
--channel. Acme’s channel sets the threshold to 50000:
Modify B: change the memory policy. Now a change that does move the plan. The
scd_type2.yaml example declares a memory budget with a backpressure policy:
No worked answer this time. The tumbling_clicks.yaml example is a three-node pipeline:
a source, an aggregate that buckets clicks into hourly windows, and an output. You
have not seen its plan.
That’s Orientation. You can build the engine, run a pipeline, work
the compiler loop, navigate the crates, and read a plan, and predict one from the YAML before
you run it. Next comes A Record’s Journey, where we follow one
record all the way through that four-node DAG and start opening the doors.
From the pipeline author’s side (optional, one-directional; the same --explain plan read from
the pipeline author’s side, no engine internals):