Embedded-first, Arrow-native stream processing engine in modern C++ (C++23). Run SQL streaming pipelines in-process in milliseconds, or submit the same file to a distributed cluster: exactly-once, event time, state as open Arrow data, deterministic replay.
4
stars
1,119
commits
C++
primary language
Sep 7, 2026
updated
clink is an embedded-first, Arrow-native stream processing engine in
modern C++ (C++23): stateful stream processing with engine-grade
semantics - SQL, event time, keyed state, exactly-once checkpoints -
that you run like a tool rather than operate like a platform.
The whole engine lives in one library, and the same pipeline runs two
ways. In-process: clink run pipeline.sql executes it in a single
process with no daemons and prints its first result about 155 ms after
process start; libclink embeds the engine in any service behind a
pure-C ABI with results as Arrow C streams; pyclink returns them as
pyarrow tables in a notebook. At scale: the same SQL file, unchanged,
submits to a distributed Coordinator/Worker cluster with parallelism,
failover, and rescale.
Three capabilities follow from that design:
clink replay
re-executes an operator over exactly those records, offline and
byte-identically, and can freeze the incident into a permanent
regression test. See replay determinism.Measured, not asserted: across the 17-query nexmark suite on a five-node cluster, clink processes an event for 1.9x to 5.3x less CPU (median 2.45x) than a JVM stream processor producing identical, correctness-gated output. Method, caveats and raw per-run data: Benchmarks, priced out in instances, dollars and modelled CO2e at Cost and environmental footprint.
clink is heavily inspired by Apache Flink. Flink's model of typed operator DAGs, event-time processing, in-band watermarks and checkpoint barriers, keyed state, and exactly-once semantics is the conceptual foundation this engine builds on. clink reworks that model in modern C++ around Arrow-native columnar execution and JVM-free deployment.
Nothing to clone, install or configure:
docker run --rm ghcr.io/orhaugh/clink-runtime:latest run /opt/clink/examples/sql/hello.sql
The pipeline and the data it reads both ship inside the image (amd64 and arm64). It groups a small stream of device readings by region and prints the result:
{"avg_reading":21.4,"peak":21.4,"readings":1,"region":"emea"}
{"avg_reading":22.65,"peak":23.9,"readings":2,"region":"emea"}
{"avg_reading":19.2,"peak":19.2,"readings":1,"region":"amer"}
...
Those rows are the point. A batch engine prints one line per region at the end; clink prints a line per update, because the aggregate is a stream and you are watching it change. Run your own file the same way by mounting it:
docker run --rm -v "$PWD:/work" ghcr.io/orhaugh/clink-runtime:latest run /work/mine.sql
Want to build something real? Follow the
Kafka to clink to ClickHouse tutorial:
one docker compose up gives you a broker, a database and a small clink
cluster running an event-time windowed pipeline; you then kill the Worker
mid-stream, watch it recover from its checkpoint, verify every result
independently, and open the job's state as an Arrow table. The example
lives in examples/kafka-to-clickhouse.
clink builds against a pinned Apache Arrow toolchain in ~/.clink-deps.
The bootstrap step downloads a prebuilt, checksum-pinned archive when one
exists for your platform (macOS arm64, Linux x86_64/arm64; about a
minute) and compiles it from source otherwise:
git clone https://github.com/orhaugh/clink && cd clink
scripts/build-arrow.sh && scripts/build-iceberg-cpp.sh # one-time, cached
cmake -S . -B build && cmake --build build --parallel 10
ctest --test-dir build --parallel 8 # optional
Then run a first pipeline - one process, no daemons:
printf '{"usr":"alice","amount":12}\n{"usr":"bob","amount":7}\n{"usr":"alice","amount":5}\n' > /tmp/orders.ndjson
./build/clink run -e "CREATE TABLE orders (usr VARCHAR, amount BIGINT) \
WITH (connector='file', format='json', path='/tmp/orders.ndjson'); \
SELECT usr, SUM(amount) AS total FROM orders GROUP BY usr"
Supported platforms: macOS (Apple Silicon is the primary development
platform) and Linux (Debian-family, exercised in CI). Windows is not
supported. ./build_and_test.sh is the reproducible CI-matching path,
including the sanitizer matrix; CONTRIBUTING.md has
the details.
Embedded. clink run pipeline.sql runs the whole engine in one
process: SQL frontend, operators, state backends, checkpointing,
connectors. First result in about 155 ms from process start, a figure
gated by a release test so it cannot silently regress. The same engine
embeds behind a pure-C ABI (libclink),
from Python (pyclink), and over
Arrow Flight SQL
for any ADBC/JDBC client.
Distributed. The same SQL file or compiled job plugin submits to a Coordinator/Worker cluster: parallel subtasks, hash-partitioned keyed shuffles, exactly-once checkpoints, failover from the last completed checkpoint, hot per-operator rescale, and rolling upgrades through savepoints. A Helm chart and Kubernetes operator ship in-tree.
clink_node --role=coordinator --rpc-port=6123 &
clink_node --role=worker --coordinator-host=127.0.0.1 --coordinator-port=6123 &
clink run pipeline.sql --coordinator-host=127.0.0.1 --coordinator-port=8081
Typed C++ pipelines (the fluent Pipeline / DataStream<T> API, keyed
process functions, windows, joins, CEP) are documented with runnable
programs under docs/consumer-examples/.
Benchmarks say how fast an engine is; they say nothing about whether its guarantees hold when processes die at the worst possible instant. clink runs a standing qualification programme: long campaigns on disposable multi-host rigs with faults injected into the narrowest windows of the engine's own protocols, judged by an independent oracle, published only when green and with the evidence retained. A few of the published results:
| Qualified | Result |
|---|---|
| Kafka exactly-once | 755/755 windows byte-exact after two hours of kills inside commit windows, coordinator SIGKILLs, broker outages and partitions |
| Large keyed state | 29 GiB of keyed state on a disaggregated backend, every key correct under the same fault battery |
| Wide job graphs | 147 operators as 292 subtasks, exactly once under faults, 28-second recovery from a worker kill at that width |
| Rolling upgrade | An engine upgrade with exactly-once continuity: 2 s savepoint, 2 s restore, every event across the boundary counted once |
| Semantic comparison | 19 of 19 queries content-equal with an independent reference engine under pre-declared judgement classes |
The full table, the rig, the method and every campaign's honesty-bounded claims: Qualification.
clink is young and pre-1.0. Its guarantees are qualified within the published bounds above, not battle-tested by years of third-party production deployments; public C++ APIs may still change between minor releases, and every such change is called out in the CHANGELOG. Durable state is treated conservatively: snapshots carry schema versions with a migrate-at-restore path, so an upgrade does not silently invalidate checkpoints or savepoints. What the engine can do, feature by feature with caveats stated, lives in the capability catalogue.
The path to 1.0 is primarily about stability and operational evidence rather than adding another broad layer of features.
Before 1.0, the priorities are:
1.0 will mean that these compatibility and operational contracts are ready to be relied upon. It will not mean that feature development stops.
See GitHub issues for work currently in progress.
Everything below is published at orhaugh.github.io/clink:
MATCH_RECOGNIZE, UDFs
and SQL-native ML.clink ships as a regular CMake package:
cmake -S . -B build -DCLINK_BUILD_TESTS=OFF -DCLINK_BUILD_EXAMPLES=OFF \
-DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build --parallel 10
sudo cmake --install build
Downstream projects consume it with find_package(clink REQUIRED) and
link clink::clink (or per-impl targets such as clink::kafka);
docs/consumer-examples/ is a complete
find_package-based project to copy from. The install carries the
clink CLI and the clink_node cluster daemon. Optional connectors and
backends are CLINK_WITH_<NAME> CMake options (default AUTO: used
when the dependency is found); the SQL frontend is CLINK_BUILD_SQL=ON
by default. clink --capabilities prints what any given binary was
built with.
clink is licensed under the Apache License 2.0. See LICENSE
and NOTICE. You are free to use clink for any purpose,
including in commercial and closed-source products. If you build on it,
a link back to this repository is appreciated; for write-ups, please
cite it via CITATION.cff.
1,118 commits
1 commits
C++
88.2%
Shell
4.9%
Python
4.2%
CMake
1.6%
Embedded-first, Arrow-native stream processing engine in modern C++ (C++23). Run SQL streaming pipelines in-process in milliseconds, or submit the same file to a distributed cluster: exactly-once, event time, state as open Arrow data, deterministic replay.
4
stars
1,119
commits
C++
primary language
Sep 7, 2026
updated
clink is an embedded-first, Arrow-native stream processing engine in
modern C++ (C++23): stateful stream processing with engine-grade
semantics - SQL, event time, keyed state, exactly-once checkpoints -
that you run like a tool rather than operate like a platform.
The whole engine lives in one library, and the same pipeline runs two
ways. In-process: clink run pipeline.sql executes it in a single
process with no daemons and prints its first result about 155 ms after
process start; libclink embeds the engine in any service behind a
pure-C ABI with results as Arrow C streams; pyclink returns them as
pyarrow tables in a notebook. At scale: the same SQL file, unchanged,
submits to a distributed Coordinator/Worker cluster with parallelism,
failover, and rescale.
Three capabilities follow from that design:
clink replay
re-executes an operator over exactly those records, offline and
byte-identically, and can freeze the incident into a permanent
regression test. See replay determinism.Measured, not asserted: across the 17-query nexmark suite on a five-node cluster, clink processes an event for 1.9x to 5.3x less CPU (median 2.45x) than a JVM stream processor producing identical, correctness-gated output. Method, caveats and raw per-run data: Benchmarks, priced out in instances, dollars and modelled CO2e at Cost and environmental footprint.
clink is heavily inspired by Apache Flink. Flink's model of typed operator DAGs, event-time processing, in-band watermarks and checkpoint barriers, keyed state, and exactly-once semantics is the conceptual foundation this engine builds on. clink reworks that model in modern C++ around Arrow-native columnar execution and JVM-free deployment.
Nothing to clone, install or configure:
docker run --rm ghcr.io/orhaugh/clink-runtime:latest run /opt/clink/examples/sql/hello.sql
The pipeline and the data it reads both ship inside the image (amd64 and arm64). It groups a small stream of device readings by region and prints the result:
{"avg_reading":21.4,"peak":21.4,"readings":1,"region":"emea"}
{"avg_reading":22.65,"peak":23.9,"readings":2,"region":"emea"}
{"avg_reading":19.2,"peak":19.2,"readings":1,"region":"amer"}
...
Those rows are the point. A batch engine prints one line per region at the end; clink prints a line per update, because the aggregate is a stream and you are watching it change. Run your own file the same way by mounting it:
docker run --rm -v "$PWD:/work" ghcr.io/orhaugh/clink-runtime:latest run /work/mine.sql
Want to build something real? Follow the
Kafka to clink to ClickHouse tutorial:
one docker compose up gives you a broker, a database and a small clink
cluster running an event-time windowed pipeline; you then kill the Worker
mid-stream, watch it recover from its checkpoint, verify every result
independently, and open the job's state as an Arrow table. The example
lives in examples/kafka-to-clickhouse.
clink builds against a pinned Apache Arrow toolchain in ~/.clink-deps.
The bootstrap step downloads a prebuilt, checksum-pinned archive when one
exists for your platform (macOS arm64, Linux x86_64/arm64; about a
minute) and compiles it from source otherwise:
git clone https://github.com/orhaugh/clink && cd clink
scripts/build-arrow.sh && scripts/build-iceberg-cpp.sh # one-time, cached
cmake -S . -B build && cmake --build build --parallel 10
ctest --test-dir build --parallel 8 # optional
Then run a first pipeline - one process, no daemons:
printf '{"usr":"alice","amount":12}\n{"usr":"bob","amount":7}\n{"usr":"alice","amount":5}\n' > /tmp/orders.ndjson
./build/clink run -e "CREATE TABLE orders (usr VARCHAR, amount BIGINT) \
WITH (connector='file', format='json', path='/tmp/orders.ndjson'); \
SELECT usr, SUM(amount) AS total FROM orders GROUP BY usr"
Supported platforms: macOS (Apple Silicon is the primary development
platform) and Linux (Debian-family, exercised in CI). Windows is not
supported. ./build_and_test.sh is the reproducible CI-matching path,
including the sanitizer matrix; CONTRIBUTING.md has
the details.
Embedded. clink run pipeline.sql runs the whole engine in one
process: SQL frontend, operators, state backends, checkpointing,
connectors. First result in about 155 ms from process start, a figure
gated by a release test so it cannot silently regress. The same engine
embeds behind a pure-C ABI (libclink),
from Python (pyclink), and over
Arrow Flight SQL
for any ADBC/JDBC client.
Distributed. The same SQL file or compiled job plugin submits to a Coordinator/Worker cluster: parallel subtasks, hash-partitioned keyed shuffles, exactly-once checkpoints, failover from the last completed checkpoint, hot per-operator rescale, and rolling upgrades through savepoints. A Helm chart and Kubernetes operator ship in-tree.
clink_node --role=coordinator --rpc-port=6123 &
clink_node --role=worker --coordinator-host=127.0.0.1 --coordinator-port=6123 &
clink run pipeline.sql --coordinator-host=127.0.0.1 --coordinator-port=8081
Typed C++ pipelines (the fluent Pipeline / DataStream<T> API, keyed
process functions, windows, joins, CEP) are documented with runnable
programs under docs/consumer-examples/.
Benchmarks say how fast an engine is; they say nothing about whether its guarantees hold when processes die at the worst possible instant. clink runs a standing qualification programme: long campaigns on disposable multi-host rigs with faults injected into the narrowest windows of the engine's own protocols, judged by an independent oracle, published only when green and with the evidence retained. A few of the published results:
| Qualified | Result |
|---|---|
| Kafka exactly-once | 755/755 windows byte-exact after two hours of kills inside commit windows, coordinator SIGKILLs, broker outages and partitions |
| Large keyed state | 29 GiB of keyed state on a disaggregated backend, every key correct under the same fault battery |
| Wide job graphs | 147 operators as 292 subtasks, exactly once under faults, 28-second recovery from a worker kill at that width |
| Rolling upgrade | An engine upgrade with exactly-once continuity: 2 s savepoint, 2 s restore, every event across the boundary counted once |
| Semantic comparison | 19 of 19 queries content-equal with an independent reference engine under pre-declared judgement classes |
The full table, the rig, the method and every campaign's honesty-bounded claims: Qualification.
clink is young and pre-1.0. Its guarantees are qualified within the published bounds above, not battle-tested by years of third-party production deployments; public C++ APIs may still change between minor releases, and every such change is called out in the CHANGELOG. Durable state is treated conservatively: snapshots carry schema versions with a migrate-at-restore path, so an upgrade does not silently invalidate checkpoints or savepoints. What the engine can do, feature by feature with caveats stated, lives in the capability catalogue.
The path to 1.0 is primarily about stability and operational evidence rather than adding another broad layer of features.
Before 1.0, the priorities are:
1.0 will mean that these compatibility and operational contracts are ready to be relied upon. It will not mean that feature development stops.
See GitHub issues for work currently in progress.
Everything below is published at orhaugh.github.io/clink:
MATCH_RECOGNIZE, UDFs
and SQL-native ML.clink ships as a regular CMake package:
cmake -S . -B build -DCLINK_BUILD_TESTS=OFF -DCLINK_BUILD_EXAMPLES=OFF \
-DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build --parallel 10
sudo cmake --install build
Downstream projects consume it with find_package(clink REQUIRED) and
link clink::clink (or per-impl targets such as clink::kafka);
docs/consumer-examples/ is a complete
find_package-based project to copy from. The install carries the
clink CLI and the clink_node cluster daemon. Optional connectors and
backends are CLINK_WITH_<NAME> CMake options (default AUTO: used
when the dependency is found); the SQL frontend is CLINK_BUILD_SQL=ON
by default. clink --capabilities prints what any given binary was
built with.
clink is licensed under the Apache License 2.0. See LICENSE
and NOTICE. You are free to use clink for any purpose,
including in commercial and closed-source products. If you build on it,
a link back to this repository is appreciated; for write-ups, please
cite it via CITATION.cff.
1,118 commits
1 commits
C++
88.2%
Shell
4.9%
Python
4.2%
CMake
1.6%