Stateless operators: the part that is still just batch
What do stateless operators get for free, and when does an operator stop being stateless?
A stateless operator produces output that depends only on the record in hand. For example, filter(order.amount > 0) or map(order -> order.userId). This one property makes it cheap in every dimension that matters here:
- Recovery state is one offset. After a crash, there is nothing to restore except "where was I in the log?".
- Rescaling moves nothing. You can run 4 or 40 copies, and there is no state to redistribute.
- Fusion is free. Consecutive stateless operators can be fused (merged) into one task, so a
filter → map → projectchain costs one chain of function calls instead of three network hops with serialization at each hop.
Stateless operators are the part of a streaming job that is still just batch.
The boundary condition. "Stateless" means no engine-managed state. It does not mean no side effects.
- A
mapthat calls a remote service is stateless to the scheduler, but its result can differ when the same record is processed again after a crash (non-deterministic on replay). This quietly breaks exactly-once (Exactly-once). - A
filterwhose predicate is updated by a broadcast control stream (a second stream of rule updates sent to every instance) is genuinely stateful. It must be declared stateful, or the predicate silently resets to its default after recovery.