Skip to content

coldchain

Monitoring for refrigerated freight. Containers report temperatures all the time, and almost none of it matters. What matters is the shipment that spent forty minutes above its limit, the container that went dark between Rotterdam and Milan, and the door left open on a loading bay. Deciding which of those happened is not a query over a table: it is a state machine per container, running in event time, with clocks that have to survive a restart.

coldchain builds that on clink's typed C++ API, with no SQL. The whole graph is one function, and both a single binary and a cluster job build from it.

Repository ยท pinned to clink v0.15.0

What it does

A generator writes three days of telemetry for forty containers, about 24,000 records out of event-time order, and the shipment assignments that say what each container carries. Twenty-one incidents are planted: eleven that must raise an alert (among them a failing cooling unit, a power loss, an open door and a container that goes silent) and ten negative controls that must not, such as a door opened briefly during loading or a single-sample sensor spike.

flowchart LR
  telemetry["Telemetry"] --> enrich["enrich<br/>keyed connect"]
  shipments["Shipments"] --> enrich
  enrich --> spec["specification<br/>async cached lookup"]
  spec --> twin["per-container twin<br/>event-time state machine"]
  spec --> journeys["journeys<br/>session window"]
  twin --> excursions["excursions"]
  twin --> silence["silence alerts"]
  twin --> late1["late records"]
  journeys --> trips["journeys"]
  journeys --> late2["late records"]

An excursion is defined by persistence: out of band, continuously, for longer than the cargo tolerates, where the tolerance depends on what is loaded. A silence alert is defined by the absence of data, so nothing arrives to trigger it. Temperatures are integer milli-degrees, so no figure depends on the order a fold happened to run in.

coldchain is clink operators from end to end; the only other moving part is an optional specification service.

What happens How clink does it Read more
Telemetry meets the shipment in force a keyed two-input connect that holds a container's early records until its assignment arrives Operator model
Each cargo's tolerance comes from a service without stalling the stream an asynchronous keyed operator with a per-container cache kept in the state backend Async state execution
Each container runs its own state machine a keyed process function with event-time timers; the silence watchdog is a timer that fires when nothing arrives Time and windowing
Records arrive out of order each container's records are reordered by event time, and a record too late to order goes to a side output instead of being processed Time and windowing
Journeys are summarised a session window with an aggregate, allowed lateness and its own late output Time and windowing
The fleet register exists before the first record a savepoint written offline, before the job has ever run Fault tolerance and rescale
One graph runs in a process and on a cluster the same define_pipeline function builds the local binary and the job a cluster deploys Embedded-first
Operators are tested in isolation clink's public test harness, with watermarks and timers driven by hand Testing framework

What a run proves

The checker never reads coldchain's code or its state. It applies the written alert rules to the raw tape from scratch and compares sets of alerts. The gate expects exactly 10 excursions, 2 silence alerts, 10 records refused by the detector as too late, 6 too late for the journey window, and 196 journeys, each with the cause and severity the tape implies.

Three scenes then change how the pipeline runs and require the same answers:

  • On a cluster: the same graph submitted to a Coordinator with two Workers is held to the same figures.
  • With the specification service on the data path: the alerts must be identical, and the service is asked 170 times for 24,021 samples; the cache answers the rest.
  • With a seeded fleet: two containers are left out of the savepoint written before the run, and exactly those two are reported as unregistered, with every other alert unchanged.

The operator tests pin the edges: a breach that recovers on exactly the sample its dwell lands on reports nothing, five readings delivered backwards produce the identical alert, and an open breach survives a snapshot and restore with its original start time.

What is distinctive here

One definition, two deployments, the same answers. The pipeline is a single C++ function: a single binary runs it in-process, a cluster runs it as a job, and neither knows which is happening. Because every rule is in event time and every figure is an integer, the checker holds both to the same exact set of alerts, so what is verified on one machine is what gets deployed.

The typed API is the other half. Rules defined by persistence and by absence are state machines with timers, which is what this API is for and what SQL would make contrived.

Run it

You need Python 3, CMake and a C++ compiler. On Linux x86_64 the pinned clink release installs as a prebuilt download; elsewhere it builds from source.

git clone https://github.com/orhaugh/coldchain && cd coldchain
scripts/get-clink.sh            # the pinned clink release, into .clink/
scripts/run-gate.sh             # tape, pipeline, checker and operator tests
scripts/scene-cluster.sh        # the same graph on a Coordinator and two Workers