tidemarkIn progress
Exactly-Once Stream Processing in Go
- Go
- Pebble
- Stream Processing
- Nexmark
Overview & Problem
Stream processors fail silently. A watermark computed as the max instead of the min fires windows on incomplete data and nothing crashes; the counts are just slightly wrong. tidemark is an event-time stream processing engine built around that fact. Correctness is the deliverable and throughput is secondary.
What I Built
Event-time windowing with watermark propagation through the dataflow graph. Each input gate's watermark is the minimum across its channels.
Hash-partitioned parallelism: a record routes to exactly one channel within an edge, while watermarks, barriers, and end-of-stream broadcast to every channel on every edge.
Chandy-Lamport barrier checkpointing with barrier alignment and Pebble-backed keyed operator state. A checkpoint is usable for recovery only after an atomic _COMPLETE marker is written last.
Exactly-once sinks that commit on checkpoint-complete notification and never during the snapshot, so recovery cannot commit data belonging to a checkpoint that never finished.
A chaos suite that injects seeded fault schedules across five Nexmark queries, and a batch oracle every windowed computation is checked against.
Architecture
A job is a dataflow graph of vertices, each running as parallel subtasks, and the subtask is the unit of scheduling, state, and failure. Watermarks and barriers travel in-band as stream elements alongside records, which keeps them ordered relative to the data they describe. Sources inject barriers at a fixed element interval rather than on a timer, and every source is a pure function of (seed, offset), so a recovering subtask can seek to its checkpointed offset and replay exactly the records it would have read. Jobs currently run as goroutines in a single process; a coordinator and worker split is a planned phase.
Key Technical Decisions & Tradeoffs
Faults are keyed to logical position (elements processed, barriers seen), never wall-clock time. Go's scheduler is not deterministic, so the fault schedule has to be, and that is what makes a crash and recovery reproducible from a single seed.
Identical output means the final sorted contents of the sink, not emission order. Ordering after recovery will differ from a clean run, so a test that compares emission order is testing the wrong property.
Every correctness test runs on a topology where the property can actually fail. A watermark bug that took the max instead of the min survived an early phase because the transport tests used a single flat output list and never exercised multiple downstream vertices.
Benchmarks are gated on a machine fingerprint. Throughput and recovery numbers publish only from a reference machine with at least 16 cores and non-rotational NVMe, and the benchmark check refuses to compare runs across different machines.
Results
State size is measured and hardware independent: on Nexmark q7 over two million events, checkpoint state is 41.0 bytes per entry at every cardinality and parallelism, and a fitted model predicted a 100 MiB checkpoint to within one percent when it was run.
Measurement surfaced a real engine property. Source subtasks split contiguous offset ranges and the gate watermark is a minimum, so peak state grows about a hundredfold from parallelism 1 to 4 on the same workload, and every state figure is quoted with its parallelism.
Throughput and recovery latency are pending a run on reference hardware, because the development machine could not produce either number honestly.