Skip to content

Operating a pipeline — run it for real

The question for this lesson: your pipeline is built and validated. Now you have to operate it: run it on real volumes without exhausting memory, know whether it actually succeeded, and run it on a schedule. How?

  • Bound a run’s memory with --memory-limit (and let Clinker spill to disk under pressure).
  • Read the exit code to tell a clean run from a partial one from a failure.
  • Find a run’s metrics and logs.
  • Schedule a pipeline with external tools, because Clinker has no built-in scheduler.

This is the mental model the whole lesson rests on: Clinker reads its inputs, processes them, writes its outputs, and exits. It is not a server and not a daemon; there is no “running Clinker” sitting in the background waiting. Every run is one finite job with a beginning and an end, exactly like grep or a backup script. That single fact decides how you bound it, watch it, and schedule it.

A batch job over a big file could try to hold too much in memory. Clinker won’t: you give it a memory budget, and when a buffering step (an aggregate, a reshape) would exceed it, Clinker spills the overflow to disk and keeps going instead of crashing.

Set the budget on the command line:

clinker run customer_etl.yaml --memory-limit 256M --threads 4

--memory-limit defaults to 512M; --threads sets how much runs in parallel. Or pin the budget in the pipeline itself. This is Clinker’s real scd_type2 example, which sets a tiny budget on purpose so even a small run exercises the spill path:

pipeline:
name: scd_type2_backfill
memory: { limit: "16K", backpressure: spill } # tight budget → spill to disk
🌱 New here? — backpressure: spill

backpressure says what to do when a step fills its memory budget. spill writes the overflow to a temp file and reads it back: slower, but the run finishes in bounded memory. (The other modes pause or do both.) The point: a job too big for RAM still completes.

Now change it. Take that comfortable --memory-limit 256M and crank the budget down to a deliberately tiny --memory-limit 16K, or set it in the pipeline the way the scd_type2 example does:

pipeline:
memory: { limit: "16K", backpressure: spill } # far below what the data needs

When the run ends, its exit code is how a script (or you) know what happened. The ones to recognize:

  • 0 is clean success. Every row made it through.
  • 2 means completed, but some rows were dead-lettered (the DLQ from lesson 07). The job did its work; you just have rows to review. This is not a failure.
  • 1 is a failure: a config/schema error, a fail-fast error, or the memory budget was blown.
  • 3 is too many bad rows (a DLQ-rate limit) or a CXL evaluation error; 4 is an I/O, format, or spill error; 130 is interrupted (Ctrl-C / SIGTERM).

To see how a run behaved (rows in/out per node, timings), spool metrics to a directory and collect them:

clinker run customer_etl.yaml --metrics-spool-dir ./metrics
clinker metrics collect ./metrics

For more detail while it runs, raise the log level:

clinker run customer_etl.yaml --log-level debug

Because Clinker is a finite batch job with no built-in scheduler or daemon, you don’t ask Clinker to “run nightly.” You run it once, and let your operating system or orchestrator do the scheduling: cron, a systemd timer, or Airflow.

# crontab — run the customer ETL every night at 02:00
0 2 * * * clinker run /etc/clinker/customer_etl.yaml >> /var/log/customer_etl.log 2>&1

That’s the whole pattern: a normal command, on a normal schedule, with its exit code and logs captured like any other batch job.