Documentation · Overview

The fastest
line down the channel.

Thalweg carries records from streaming and batch sources into analytical and operational stores — with the whole path, from wire to table, memory- and type-safe in one language.

thal·weg  /ˈtɑːlveɡ/  — the line of fastest flow, the deepest continuous channel down a watercourse. In law, the boundary drawn along a river: the line that carries the authority.

TWG — short for Thalweg, and read as Throughput With Guarantees: move a great deal of data, and never lose any of it.

43
Crates
44
Decisions recorded
3
Record representations
100%
Pure Rust*

*Two documented exceptions, both opt-in and off by default. Every other path is pure Rust with no C dependency on the hot path.

01How it works

A record enters from a source — a broker, a file, an Arrow stream. It is decoded once into a shared columnar form, checked against its data contract, transformed by SQL if you want it to be, and written to one or more sinks. Along the way every record carries its origin, so if a destination falls behind it can be rebuilt from the durable copy rather than re-read from the source.

SOURCE Kafka · Pulsar files · Flight DECODE → Arrow once CONTRACT ODCS + DQ clean/reject TRANSFORM SQL DAG WASM · Flight SINKS Postgres Delta · files PRIMARY RAW durable · acks source offset raw-anchored recovery — rebuild a lagging sink without re-reading the source ONE RECORD'S JOURNEY left to right — source offset advances only once the primary-raw sink confirms the write is durable

Every box on the channel is a stage you can observe independently — its throughput, its memory, and whether it's healthy or building backpressure — at /health and /stats.

02What's supported

Thalweg is pre-implementation: the architecture is decided and the workspace is scaffolded, but code has not yet been written. The table below is honest about that. It shows where each capability stands today, and what is planned but not yet started — for example RabbitMQ, NATS, and other transports are planned, not built.

Design complete Scaffolded Partial Planned

Sources

CapabilityStatusNotes
Apache KafkaScaffoldedPure-Rust client; consumer groups incl. cooperative-sticky
Apache PulsarScaffoldedFresh binary-protocol client; staged subscription and memory-aware shedding designed in
Batch filesScaffoldedParquet, CSV, JSON, XML, Excel, Avro OCF
Arrow Flight (gRPC)ScaffoldedAlready-Arrow, zero decode
Arrow IPC (file & stream)ScaffoldedNon-Flight raw IPC
Delta Sharing (recipient)ScaffoldedCross-org read via pre-signed URLs
Arrow C Data InterfaceScaffoldedIn-process zero-copy handoff from PyArrow/Go/C++/Java
RabbitMQPlannedNot yet started
NATS / JetStreamPlannedNot yet started
AWS KinesisPlannedNot yet started
MQTTPlannedNot yet started
REST (poll / webhook)PlannedNoted for future consideration
WebSocket (WSS)PlannedNoted for future consideration

Payload formats

FormatStatusNotes
Avro (registry + OCF)ScaffoldedConfluent / Glue / Apicurio wire framing; OCF shares decode
ProtobufUnder reviewDecode strategy open: zero-copy descriptor-driven parser, gated on licence + benchmark
JSONScaffoldedSchema-driven + inferred
XMLScaffoldedquick-xml + bespoke XSD/contract-driven mapping
Custom binary (WASM)ScaffoldedBatch-oriented WASM decoder; native compile-in exception

Sinks & table formats

TargetStatusNotes
PostgreSQLScaffoldedBinary COPY; Postgres 18+; primary-raw eligible
Databricks (Zerobus)ScaffoldedArrow Flight → Delta; primary-raw eligible where the table has Iceberg reads enabled, read back via the catalog's Iceberg REST endpoint
Parquet (object store)ScaffoldedPlain files; primary-raw eligible, no catalog needed
Apache IcebergScaffoldedRead + write, catalog-mediated. The one open table format carried; reader feeds recovery
Arrow Flight / IPCScaffoldedFirst-class out-transports
DuckLakePlannedSource & sink; treated like other DB targets
REST / WebSocketPlannedPush/forward sink; noted for future consideration

Platform capabilities

CapabilityStatusNotes
Data contracts (ODCS)ScaffoldedSchema authority + quality-rule merge; local/HTTP/object-store
DQ audit trailScaffoldedFull-grain per-record per-rule; durable sink; on by default
Raw-anchored recoveryDesign completeBounded windowed replay, dedup on record ID; runs as a separate scale-to-zero role
SQL transforms (DataFusion)ScaffoldedDAG with interior sinks, materialised intermediates
Health & observabilityScaffoldedPer-stage health tree, OTLP, Prometheus, /stats
K8s / GitOps configScaffoldedVerbatim TOML delivery; secret enumeration
Pulsar flow controlDesign completeStaged subscription, topic-spread shedding, byte-budget admission, pressure-as-scale-signal
Java / Python facades (C ABI)Design completeReserved; deferred (design-for, don't-build)

03Design principles

Five ideas shape every decision in Thalweg. They are worth reading before the architecture, because the rest follows from them.

Pure Rust, sans-io edges

No C-linked broker client on the hot path. Codec and auth crates carry no async runtime, so they are reusable anywhere — a Lambda, a query engine, a transport not yet written.

Three co-equal representations

Raw bytes, a neutral value model, and Apache Arrow — none privileged. Pass through untouched, decode per-record, or decode straight to columnar batches.

One connector contract

Kafka and Pulsar implement the same Source/Sink interface, so flow control, health, retry, and recovery are written once and shared across every transport.

Raw is the system of record

Any replayable sink can hold the primary-raw role. The source is acknowledged only once raw is durable; lagging sinks rebuild from raw, never by re-reading the source.

Every decision is written down

Forty-four architecture decision records, a scenario catalogue, and encoded regression tests — the hard-won lessons are permanent, not tribal knowledge.

Structural rules are mechanical

The layering, the single Arrow version, the confinement of unsafe code — all enforced in CI, so the architecture cannot erode silently under delivery pressure.

04The layers

Crates are grouped into layers with a strictly downward dependency direction, enforced in CI. Nothing in the codec layer may depend on a transport; nothing in the codec or SASL layer may depend on an async runtime.

COMPOSE kafka-arrow · pulsar-arrow · cli (twg) FORMATS parquet · iceberg SINKS postgres · zerobus · object-store PIPELINE stream-arrow · pipeline-sql · pipeline-wasm · pipeline-flight CONTRACTS contract-core · contract-odcs · dq-enforcer · dq-audit CODEC codec-core · type-map · avro · protobuf · json · xml · custom FRAMING schema-registry · table-catalog CONNECTOR connector-core SOURCES kafka-client · pulsar-client · source-batch · source-flight · … TRANSPORT wire-tls · wire-sasl · wire-compression (no runtime, no format)

Reading bottom to top: raw bytes come in through transport primitives, up through sources into the shared connector contract, get decoded and validated, transformed, and written out. The codec-core crate holds the single Arrow re-export — the one place the Arrow version is named, so the whole workspace pins one version.

05Crate reference

Forty-three crates. Filter by layer, or read straight through. Every crate is currently a scaffold — the descriptions are the contract each will fulfil.

06Recovery & guarantees

The correctness backbone is one rule: the source offset advances only after a sink has durably acknowledged the record — never on send, never on buffered. In a multi-sink setup, the sink that gates the source is the one holding the primary-raw role. Everything else recovers from raw.

Why this matters to you. A slow or failed destination never blocks your source and never loses data. The record is safe in raw the moment the source advances, and any lagging sink is rebuilt from raw by a bounded, deduplicated replay — automatically. Turn raw off and you trade that guarantee for simplicity; the config tells you when you're doing so.

durable ack

Server-side ack (Zerobus), transaction commit (Postgres), or flush+close (Parquet) — before the source advances.

deterministic IDs

Each record hashes to a stable ID including its source cluster, so replay deduplicates perfectly.

bounded windows

Recovery replays in time- and row-bounded windows, so catch-up never runs memory away.

unified DLQ

Decode, contract, and quality failures land in one dead-letter path, tagged by reason — never mixed with dropped test traffic.

07Decision records

Every significant choice is recorded as an ADR, so the reasoning survives the people. Four remain genuinely open, pending trade studies or production input; the rest are decided and awaiting drafting into full prose.

Open decision: protobuf decode benchmark. These are flagged as open rather than guessed at.

The full index of all 44 records lives in docs/adr/README.md. Each will be drafted in the house ADR style and synced to Confluence.

08Why this name

The thalweg is the line of fastest flow — the deepest continuous channel down a watercourse, the path water takes when it has a choice. For an engine whose job is the efficient path from source to destination, that is the thesis in one word.

It carries a second meaning that happens to describe a real design decision. In international law, the thalweg is the boundary drawn along a river: the line that carries the authority. That is precisely how this system treats its primary-raw lane — one sink holds the authoritative copy, the source is acknowledged against it, and everything else is reconciled back to it.

TWG is the consonant skeleton of T-ha-L-we-G, so it reads as short for Thalweg. It also expands to Throughput With Guarantees — and those guarantees are specific rather than decorative: the source advances only on a durable sink acknowledgement, deterministic record IDs make replay idempotent, raw-anchored recovery rebuilds lagging destinations, and data contracts enforce shape at the boundary.

The project was drafted under the working name Millrace. That name turned out to be published on crates.io already — by another streaming event system, a direct collision in this project's own domain — so it was changed before anything was pushed or published. Thalweg is also a watercourse term, which is why the channel diagrams, the sluice-gate stages, and the weathered-copper palette on this page all survived the rename intact.

What else was considered

Other water-engineering candidates were checked for availability and brand collisions. penstock had the best literal meaning — the pressurised conduit feeding a turbine — but collides with a healthcare vendor whose product is audit software, an unfortunate neighbour given this system's own audit trail. headrace collides with an HR-tech company. adit was free but reads as a mis-typing of "audit". sluice, weir, leat, grist, winnow, spillway, braid and riffle were all already published crates.

A bare three-letter acronym with no parent word was considered and rejected: the available combinations were either colliding — trc and drc are smart-pointer crates, tde is Transparent Data Encryption, avc is H.264 video — or characterless. Deriving the prefix from a real word gives both a memorable name and a short prefix. Within that, thw was rejected in favour of twg: it is the well-known abbreviation for Technisches Hilfswerk, Germany's federal civil-protection agency, an awkward adjacency for a German-named organisation.

Crates are prefixed twg-; the command is twg, with twg stream, twg batch and twg config as subcommands.

09Document map

Where to find things in the repository.

You want…Look in
This overview & navigationdocs/site/index.html
Why a decision was madedocs/adr/
The delivery plan & phasesdocs/blueprints/
How config & GitOps workdocs/operations/CONFIG.md
Health & observability modeldocs/operations/HEALTH.md
Batch reader library choicesdocs/operations/BATCH-READERS.md
Test scenarios & regressionsdocs/testing/
Working here as an agentAGENTS.md