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.

Clinker accounts retained state and responds to pressure with admission refusal, backpressure, spilling, or a controlled failure. Spilling helps when an operator can release enough memory; it does not guarantee that every workload fits every budget. Source decoding, writer preparation, irreducible state, and the process baseline still need space.

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

The default memory limit is 512M. You can also set it in the pipeline:

pipeline:
name: customer_etl
memory: { limit: "256M", backpressure: spill }
🌱 New here? — backpressure: spill

The spill policy prefers freeing retained records to disk. A producer-pausing policy also needs a satisfiable budget above measured baseline RSS; otherwise it could wait forever for memory the process cannot release. Spill-only mode can attempt progress below that baseline, but can still fail to admit required state. Neither mode turns the configured limit into an OS-enforced RSS ceiling.

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

  • 0 is clean completion. Filters and routing may intentionally omit rows.
  • 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. Whether that is acceptable depends on your data-quality policy.
  • 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.