Configuration¶
Reference for every parameter on Turbine(...) and @app.subscribe(...). For end-to-end usage examples, see Quick Start; for operational concerns (cluster mode, perf tuning, rolling upgrades) see Deployment.
Turbine(...) constructor¶
app = Turbine(
brokers="localhost:9092",
state_dir="/tmp/turbine",
state_format="json",
checkpoint_url=None,
app_id=None,
# ...
)
Broker & data¶
| Parameter | Type | Default | Description |
|---|---|---|---|
brokers |
str |
"localhost:9092" |
Kafka bootstrap servers (comma-separated host:port list). |
from_earliest |
bool |
False |
Start consuming from the earliest offset when no committed offset exists. Mutually exclusive with from_latest. |
from_latest |
bool |
False |
Start consuming from the latest offset when no committed offset exists. Mutually exclusive with from_earliest. |
max_lag_seconds |
int \| None |
None |
Hard cap on consumer lag (seconds). When set, the worker skips ahead if it falls further behind than this. Also configurable via TURBINE_MAX_LAG_SECONDS. |
State & checkpointing¶
| Parameter | Type | Default | Description |
|---|---|---|---|
state_dir |
str |
"/tmp/turbine" |
Local directory for the RocksDB state store. One subdirectory per partition. |
state_format |
str |
"json" |
Serialisation codec for values written through state.get / state.put. Accepts "json" or "msgpack". get_bytes / put_bytes bypass this entirely. |
checkpoint_url |
str \| None |
None |
Snapshot destination. Accepts file://..., s3://..., memory://.... Defaults to file://{state_dir}/snapshots at run time. |
Exactly-once¶
| Parameter | Type | Default | Description |
|---|---|---|---|
app_id |
str \| None |
None |
Stable identifier for this deployment, used to fence previous instances on restart. Required as soon as any subscribe uses processing_guarantee="exactly_once". See Delivery Guarantees. |
REST API & cluster¶
| Parameter | Type | Default | Description |
|---|---|---|---|
api_port |
int \| None |
8400 |
Port for the REST/console API. Env var: TURBINE_API_PORT. |
raft_node_id |
int \| None |
None |
Node id within the Raft cluster. Env: TURBINE_RAFT_NODE_ID. |
raft_listen_addr |
str \| None |
None |
Address this node listens on for Raft RPC (http://host:port). Env: TURBINE_RAFT_LISTEN_ADDR. |
raft_peers |
list[tuple[int, str]] \| None |
None |
Bootstrap mode peer list. Mutually exclusive with raft_seed. Env: TURBINE_RAFT_PEERS (format "1=http://a:8400,2=http://b:8400"). |
raft_seed |
str \| None |
None |
Join mode — address of an existing node. Mutually exclusive with raft_peers. Env: TURBINE_RAFT_SEED. |
Standalone (single-process) deployment leaves all raft_* parameters None. See Deployment → Raft cluster for the bootstrap / join workflow.
@app.subscribe(...) decorator¶
@app.subscribe(
kafka.topic("input-topic"),
output=kafka.topic("output-topic"),
batch_size=1000,
batch_timeout_ms=250,
partition_key="user_id",
processing_guarantee="at_least_once",
)
def handler(batch, state): ...
Topic & batching¶
| Parameter | Type | Default | Description |
|---|---|---|---|
input |
kafka.topic(...) |
(required, positional) | Kafka topic to consume, as a broker topic reference (e.g. kafka.topic("orders")). Bare strings are rejected. How the input is read (schema, event time, decode-error policy) is configured on this reference — see Input topic — kafka.topic(...). |
name |
str \| None |
(handler name) | Identifies the subscription. Must be unique per topic and stable across restarts — see Several subscriptions on one topic. |
output |
kafka.topic(...) \| None |
None |
Output topic, as a broker topic reference (e.g. kafka.topic("scores")). Omit for a sink (the handler must return None). |
batch_size |
int |
1000 |
Maximum records assembled before the handler is invoked. |
batch_timeout_ms |
int |
250 |
Maximum wait (ms) before flushing a partial batch when traffic is light. |
Several subscriptions on one topic¶
More than one handler may subscribe to the same topic. Each is an
independent consumer: it sees every record of the topic (the partitions
are not shared out between them), keeps its own state and offsets, and has its
own output and processing_guarantee. One subscription lagging, failing
or restarting does not hold up the others.
@app.subscribe(kafka.topic("events"), output=kafka.topic("scores"))
def score_users(batch): ...
@app.subscribe(kafka.topic("events"), output=kafka.topic("audit")) # same input, own output
def audit_trail(batch): ...
Use this when two concerns happen to share an input but have nothing else in common. The cost is that the topic is read and decoded once per subscription; if you only want to split one stream by a predicate, a single handler that returns different rows is cheaper.
name identifies the subscription and defaults to the handler's name. It must
be unique per topic — Turbine refuses to start otherwise — and stable
across restarts, because it anchors where Turbine records that
subscription's progress. Renaming the handler therefore makes the subscription
resume from auto.offset.reset instead of where it left off; pass an explicit
name="…" to keep the identity pinned while the function name changes.
Cluster mode
Multiple subscriptions on one topic are single-node only for now. An
app configured for the Raft cluster refuses to start with more than one
subscription per topic. Run those subscriptions as separate Turbine apps
with distinct app_ids instead.
Decoding, event time & decode errors¶
How to read the input — the schema, event-time source, and decode-error policy —
is configured on the input topic reference itself, not on subscribe. Pass
these to kafka.topic("name", ...); see Input topic — kafka.topic(...)
below for the full list.
Partitioning & parallelism¶
| Parameter | Type | Default | Description |
|---|---|---|---|
partition_key |
str \| None |
(unset) | Name of the column that carries the partitioning key. Required for rescaling (stateful subscriptions without it cannot be rebalanced cleanly) and for parallelism > 1. Pass None explicitly to silence the rescale-readiness warning when running unkeyed on purpose. |
parallelism |
int |
1 |
Number of in-process shards per Kafka partition. Records sharing the same partition_key value always land on the same shard, so each shard owns a disjoint slice of the keyspace and runs in parallel with the others. Requires a class-based handler (the runtime needs one independent instance per shard). |
See Partitioning for the divisor-of-960 constraint on Kafka topic partition counts and the rationale behind sub-partition parallelism.
Delivery guarantees¶
| Parameter | Type | Default | Description |
|---|---|---|---|
processing_guarantee |
str |
"at_least_once" |
Producer semantics for this subscription. "exactly_once" opts into Kafka transactions and requires output plus Turbine(app_id=...). See Delivery Guarantees. |
on_crash_recovery |
str |
"accept" |
Crash-recovery policy. "accept" (default) boots with the observed state gap; "replay" silently re-consumes the gap to rebuild state before resuming with outputs enabled. EOS-only — raises ValueError on at-least-once subscribes. See Crash recovery. |
halt_if_gap_exceeds |
int \| None |
None |
Circuit-breaker: if the boot-time state gap exceeds this many events, the worker refuses to start with a fatal error. EOS-only. Pair with on_crash_recovery if you want both an automatic recovery and a hard cap on catastrophic gaps. |
Error handling¶
The decode / processing error policy (on_error, dlq) is set on the input
topic — see Input topic — kafka.topic(...). The
on_compute_error policy below governs expression failures over already-decoded
rows and stays a subscribe parameter.
| Parameter | Type | Default | Description |
|---|---|---|---|
on_compute_error |
str |
"null" |
What to do when an expression kernel fails on bad data (integer divide-by-zero, strict cast overflow, parse_ts parse failure). "null" (default): null just the offending rows, count them on turbine_compute_errors_total{op}, and process the rest of the batch (SQL-faithful — a nulled row drops out of skip-null aggregates). "fail": re-raise, for correctness-critical pipelines. Distinct from on_error (on the input topic), which governs undecodable messages and handler/processing errors; this governs computation inside aggregation expressions over decoded rows. See Error handling. |
Input topic — kafka.topic(...)¶
The first positional argument of @app.subscribe(...) is a broker topic
reference. Beyond the topic name, it carries how to read the input: the
decode schema, the event-time source, and the decode-error policy. These seven
kwargs live here (not on subscribe), because they describe the input stream
itself. The matching kafka.topic(...) used for output= accepts only the
topic name and key= — these input-only properties are rejected there.
events = kafka.topic(
"events",
schema=Event, # Pydantic model → schema-aware JSON decode
event_time="event_ts", # event-time column
event_time_unit="ms",
on_error="dlq",
dlq="events-dlq",
)
@app.subscribe(events, output=kafka.topic("scores"), partition_key="user_id")
def score(batch): ...
Inline them when there are only one or two; bind the topic to a variable first (as above) when several add up.
| Parameter | Type | Default | Description |
|---|---|---|---|
schema |
type \| None |
None |
Pydantic BaseModel subclass — its fields are converted to a pyarrow.Schema for schema-aware JSON decoding (faster, no per-batch inference). Supported field types: str, int, float, bool, bytes, Optional[X], and arrays list[X] / tuple[X, ...] (→ Arrow list<X>, nesting allowed). |
avro_schema |
str \| None |
None |
Avro writer schema (JSON string). When set, payloads are decoded as Avro Single-Object Encoding frames. Wins over schema if both are set. |
event_time |
str \| None |
None |
Dotted path to the record field carrying the event timestamp. Every window under the subscription then uses it as its time column — see Windowing → time model. |
event_time_unit |
str |
"ms" |
Unit of an integer/float event_time field ("s", "ms", "us", "ns"). Ignored for Timestamp and RFC 3339 string fields. |
with_kafka_timestamp |
bool |
False |
Append an Int64 column named _kafka_ts_ms to each batch, carrying the broker-side message timestamp. Use it as the time_column= of a window to switch from processing-time to event-time semantics. |
on_error |
str |
"fail" |
What to do with a message that can't be decoded (poison pill) or a batch whose handler raises (processing error). "fail" (default): stop the app cleanly and exit non-zero, naming the offending topic/partition/offset (decode) or offset range (processing). "skip": drop the bad message (decode) or the whole batch (processing), count it on turbine_dlq_messages_total, log a throttled sample, and keep going. "dlq": route the raw payload(s) to the dlq topic (source metadata + turbine_dlq_phase in headers), then keep going. Decode failures are isolated per-row; a processing error is batch-level (one vectorised handler call). See Error handling. |
dlq |
str \| None |
None |
Dead-letter topic for on_error="dlq". Required when on_error="dlq", rejected otherwise. The original key + raw payload are preserved (replayable); source topic/partition/offset/error ride Kafka headers. |
Environment variable summary¶
For deployments where parameters are injected by an orchestrator rather than hard-coded, the following constructor arguments have environment variable equivalents (constructor argument wins when both are set):
| Constructor | Environment variable |
|---|---|
api_port |
TURBINE_API_PORT |
max_lag_seconds |
TURBINE_MAX_LAG_SECONDS |
raft_node_id |
TURBINE_RAFT_NODE_ID |
raft_listen_addr |
TURBINE_RAFT_LISTEN_ADDR |
raft_peers |
TURBINE_RAFT_PEERS |
raft_seed |
TURBINE_RAFT_SEED |
A few EOS-specific tunables don't have a constructor equivalent — they live only as env vars because they're operational knobs, not API contracts:
| Environment variable | Default | Description |
|---|---|---|
TURBINE_POINTER_WAIT_MS |
2000 |
Maximum time (ms) the cold-restart boot path waits for a Raft-replicated snapshot pointer to appear before falling back to listing the object store. Covers the cross-node replication latency on graceful handoffs in cluster mode. Set to 0 to disable the wait entirely (lower deployment latency, slightly larger state gap on cluster handoffs). |