Skip to content

Turbine-Bench

Benchmark results of Turbine against other Python stream processors under identical workloads: the same synthetic input topic, the same Redpanda broker, the same machine, one product at a time. The figures are meant for relative comparison between products, not as absolute numbers.

Every bench answers two questions:

  1. Capacity of one Kafka partition — how much flow a product extracts from a single input partition (consumed/s), spreading it over cores with its own in-process mechanism. A broker hop (repartition or changelog topic) is not hidden but counted (extra rec/rec).
  2. Efficiency — what that flow costs: rows/s per core, rows/s per GB of memory, µs CPU per record.
Drain: one prefilled Kafka partition, one process, parallelism = 3 Redpanda bench-input1 partition · prefilled, no producer running warmup measured window: consumed/s = Δ committed offset ÷ time bench-outoutput topic Stream processorone process · one consumer parallelism = 3 task 1 task 2 task 3 consume produce

How a capacity figure is read (drain): the partition is filled first, then one process drains it with no producer running. parallelism = 3 is each product's own in-process exchange — Turbine shards, Flink slots, Arroyo subtasks, RisingWave actors; Quix and FastStream have none and run on one core.

Performance at-least-once

bench what it measures state window cardinality inference delivery
inference What a framework costs around a forward pass: one frozen MLP scores every record — ONNX Runtime in the engine (Turbine's infer= operator, a Flink Java UDF, an Arroyo Rust UDF) vs eager PyTorch in Python stateless — — MLP 4-16-1 → risk at-least-once
stateless Raw I/O + decode + per-record compute ceiling: filter, enrich, republish — fed beyond each product's capacity stateless — — — at-least-once
windowed-lowcard Decode + per-record compute: a per-region SLO rollup of 12 aggregates over 9 columns — capacity drained from a backlog, close latency live at each product's knee keyed tumbling 30 s, event time 16 keys (region) — at-least-once
windowed-highcard The keyed-state store: count + avg per user over the same 30 s window, one open window per active user, same key pool for every product keyed tumbling 30 s, event time 500k keys (user_id) — at-least-once

Performance exactly-once

bench what it measures state window cardinality inference delivery
inference What a framework costs around a forward pass: one frozen MLP scores every record — ONNX Runtime in the engine (Turbine's infer= operator, a Flink Java UDF, an Arroyo Rust UDF) vs eager PyTorch in Python stateless — — MLP 4-16-1 → risk exactly-once
stateless Raw I/O + decode + per-record compute ceiling: filter, enrich, republish — fed beyond each product's capacity stateless — — — exactly-once
windowed-lowcard Decode + per-record compute: a per-region SLO rollup of 12 aggregates over 9 columns — capacity drained from a backlog, close latency live at each product's knee keyed tumbling 30 s, event time 16 keys (region) — exactly-once
windowed-highcard The keyed-state store: count + avg per user over the same 30 s window, one open window per active user, same key pool for every product keyed tumbling 30 s, event time 500k keys (user_id) — exactly-once

Quality of service

bench what it measures state window cardinality inference delivery
latency (at-least-once) End-to-end latency (p50 / p99 / p99.9) of the stateless app, every product fed the same rate it keeps up with stateless — — — at-least-once
latency (exactly-once) End-to-end latency (p50 / p99 / p99.9) of the stateless app, every product fed the same rate it keeps up with stateless — — — exactly-once
latency-inference (exactly-once) End-to-end latency (p50 / p99 / p99.9) of a fraud score on every record, published exactly once — every exactly-once line fed the same rate, its batches and commits set for latency stateless — — MLP 4-16-1 → risk exactly-once
crash-recovery Each product's strongest delivery guarantee under SIGKILL — its exactly-once claim, or at-least-once for one that makes none (RisingWave: its Kafka sink has no transactions): a backlog drained while its process tree is killed and restarted on its own state, the output audited pane by pane against an oracle recomputed from the input keyed tumbling 30 s, event time 500k keys (user_id) — each product's strongest

Last updated: 2026-11-12.

Reading a result page
  • regime — keep-up: consumed ≈ offered with a flat backlog, a sustainable point (the only one whose latency is shown); saturated: fed beyond capacity, so consumed/s IS the ceiling; drain: a static backlog drained with no producer running (--prefill), the consume ceiling free of the generator's CPU.
  • Bars scale to the column's largest value among the rows shown; they are drawn on consumed/s, rows/s per core and rows/s per GB only — raw CPU and memory sit in their own table, never bar-charted across products fed different rates.
  • Fairness — the consumer fetch configuration is the same for every client that can take it, every product runs at-least-once with an idempotent producer, and each page's sanity check compares output ÷ consumed against theory.
  • Details — each page folds what explains the numbers (the products' characteristics, column definitions, product notes, sanity): click a block's title to unfold it.