Build & run Clinker
The fastest way to start understanding a system is to run it once, end to end, and watch it do something real. Before we open a single source file, let’s get Clinker compiling and move some actual data through it, then read what the engine tells us it did.
What you’ll be able to do
Section titled “What you’ll be able to do”- Build the engine from a clinker checkout with one
cargocommand. - Run the example pipeline two ways:
--explain(plan only, no data moves) and--dry-run(data moves, output goes to the terminal). - Read an execution plan and name what each line reports: the DAG node count, the transform count, and the run mode.
- Read the
--dry-runsummary line and name what each oftotal / ok / written / dlqcounts. - Predict how the plan and the summary change when you alter one flag (
-n) or one declared variable ($vars.gold_threshold), then verify against the real output.
New terms in this lesson (each is named here before it appears below):
- pipeline: a whole job, described in YAML.
- node, one step in that job (a source, a transform, an output).
- DAG: the directed, acyclic graph the nodes form.
- CXL, the small expression language a transform runs per record.
- plan: the compiled, validated form of the job, printed by
--explain. - dead-letter queue (DLQ), where rejected records go; the
dlqcount in the summary. - dry-run: a real run whose output is redirected to your terminal.
What Clinker is (the one-paragraph version)
Section titled “What Clinker is (the one-paragraph version)”Clinker is a bounded-memory, single-process batch executor for finite ETL jobs.
You describe a job as a pipeline in YAML: a set of
nodes (a source, some
transforms, an output) wired into a graph. Per-record logic is written in a small
expression language called CXL. You hand the pipeline to the clinker command;
it reads records from the source, pushes them through the graph, writes the output,
and exits. Finite and batch: the sources end, the job drains, the process stops.
That’s the whole mental model for now. We’ll earn every word of it over the coming lessons.
The example we’ll run
Section titled “The example we’ll run”Clinker ships runnable example pipelines. We’ll use the canonical one:
clinker ·customer_etl.yaml example @19acdcb4
It’s a customer ETL job: a CSV of customers in, a CSV of flagged customers out, with two transforms in between.
nodes: - type: source # read customers.csv name: customers config: { type: csv, path: ./data/customers.csv, ... }
- type: transform # add an is_active flag name: active_only input: customers config: cxl: | emit is_active = status == "active"
- type: transform # classify into a gold/standard tier name: final_flag input: active_only config: cxl: | emit tier = if lifetime_value.to_int() > $vars.gold_threshold then "gold" else "standard"
- type: output # write the result name: results input: final_flag config: { type: csv, path: ./output/customers.csv }Four nodes (source → transform → transform → output), a tiny pipeline that is
nonetheless a complete Clinker job. The two transforms run
CXL
per record; $vars.gold_threshold is a declared variable (default 10000) the
pipeline reads instead of hard-coding the cutoff. The input is small and
human-readable:
customer_id,first_name,last_name,email,status,lifetime_value,zip_code1001,Alice,Chen,alice.chen@acme.com,active,15200,941031002,Bob,Martinez,bob.m@globex.com,active,8400,100011003,Carol,Johnson,carol.j@example.com,inactive,3200,60601Alice is active with a lifetime value above the gold_threshold (default 10000),
so she’ll be flagged gold; Bob is active but below it, so standard; Carol is
inactive.
Build it
Section titled “Build it”Clinker pins its toolchain (a rust-toolchain.toml selects the exact Rust version),
so rustup installs the right compiler automatically the first time. From your
clinker checkout:
cargo build -p clinkerThe first build compiles the whole workspace and takes a few minutes; after that, builds are incremental and fast. (The compiler loop is all about that fast inner loop.)
Predict first
Section titled “Predict first”Before you run anything, predict what the engine will say. You’ve read the four-node pipeline above; that’s enough to call the plan.
Run it
Section titled “Run it”Two ways to run a pipeline. Start with the one that doesn’t touch any data.
1. See the plan, without executing (--explain). Run the example from the
examples/pipelines/ directory (so the pipeline’s ./data/... paths resolve):
cd examples/pipelinescargo run -p clinker -- run customer_etl.yaml --explainClinker compiles the pipeline into an execution plan and prints it, but runs nothing:
=== Execution Plan ===
Mode: StreamingTransforms: 2Output projections: 1DAG nodes: 4arbitration: BackPressurePreferred -> Priority
Source DAG: Tier 0: customersFour DAG nodes, two transforms, “Streaming” mode: exactly the three numbers you predicted. You’re looking at the plan, the proof that the job is well-formed, before any record moves. We’ll come back to this view in Read a plan with —explain, and to why planning is separate from running much later.
2. Actually move data (--dry-run). A
dry run
processes records and writes the result to your terminal instead of to the output file:
cargo run -p clinker -- run customer_etl.yaml --dry-run -n 5INFO clinker: Pipeline complete: 5 total, 5 ok, 5 written, 0 dlqFive records in, five processed, five written, zero rejected, and the process
exits 0. That summary line is Clinker telling you the finite job ran clean, counted
four ways:
total: records read from the source.ok: records that passed through every node without error.written: records that reached the output.dlq: records routed to the dead-letter queue (rejected, a bad row that couldn’t be processed). Zero here means nothing was rejected; we’ll meet the DLQ properly soon.
The -n 5 is a flag that caps the run at five records. Drop it and the whole source
runs; raise or lower the number and the counts move with it. That’s exactly the
knob the apply section below has you turn.
Apply: Use → Modify → Create
Section titled “Apply: Use → Modify → Create”You’ve run the example as given (Use). Now change one thing at a time and predict the new output before you run it. That predict-then-check loop is how you learn to read the engine, not just operate it.
Modify 1: turn the -n knob
Section titled “Modify 1: turn the -n knob”Predict, then run. You ran --dry-run -n 5 and saw 5 total, 5 ok, 5 written, 0 dlq.
Now change the cap to -n 2:
cargo run -p clinker -- run customer_etl.yaml --dry-run -n 2Predict first: what does the summary line read now?
Modify 2: change a declared variable
Section titled “Modify 2: change a declared variable”The pipeline classifies a customer as gold when lifetime_value > $vars.gold_threshold.
The threshold is a declared variable at the top of the YAML:
pipeline: vars: gold_threshold: { type: int, default: 10000 }Bob’s lifetime value is 8400, so at the default he’s standard. Lower the declared
default below Bob’s value (edit the file, change 10000 to 8000), then re-run the dry
run:
cargo run -p clinker -- run customer_etl.yaml --dry-run -n 5Predict first: does changing the threshold change any of the four summary counts?
And does Bob’s tier change?
Create: a predict-then-verify of your own
Section titled “Create: a predict-then-verify of your own”Now reason about the plan with no worked answer in front of you. Before running
anything, write down what --explain would report for DAG nodes and
Transforms if you imagine adding one more type: transform node to the pipeline
(you don’t have to edit the file, just predict the two numbers). Then re-run
cargo run -p clinker -- run customer_etl.yaml --explainon the unmodified four-node pipeline and check your reasoning against the real plan
(DAG nodes: 4, Transforms: 2): your predicted five-node numbers should be exactly
one higher in each line.
Retrieval checkpoint
Section titled “Retrieval checkpoint”You just ran a four-node DAG end to end and learned to read both what the engine
plans to do (--explain) and what it did (--dry-run). Each of those nodes (the
source, the two CXL transforms, the output) is a door we’ll open in later lessons.
Next: the fast edit-and-check loop you’ll live in while working
on the engine.
From the pipeline author’s side (optional, one-directional; the same example job taught from the YAML side, no engine internals):