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:
- 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). - Efficiency — what that flow costs:
rows/s per core,rows/s per GBof memory,µs CPU per record.
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, soconsumed/sIS 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 coreandrows/s per GBonly — 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.