Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

State

GROUP BY customer_id asks the runtime to remember something: every customer’s running values. That memory is part of the program, not an implementation detail, and Streamform describes it precisely on the node that holds it.

What a node stores

An Aggregate node keeps, per key, one accumulator per aggregate call. explain shows the description:

state=Keyed
fingerprint=aggregate key=[customer_id] accumulators=[sum(amount): decimal(38,9)] retention=unbounded
  • Key: the grouping columns, in order. State is partitioned by it.
  • Accumulators: one per call, named by Streamform’s rendering of the call (sum(amount), count(*)) and typed by what is stored.
  • Retention: how long a key’s state is kept after its last event, in event time. unbounded unless the model declares retention (see Time); expiry is silent, and the key’s next event starts over.

Every other operator today is Stateless.

Accumulator types are Streamform’s rule

The stored type is derived from the argument’s declared type, by a rule Streamform owns:

CallArgument typeAccumulator
count(...), count(*)anythingint64
sum(x)int32, int64int64
sum(x)decimal(p,s)decimal(38,s): the widest precision at the declared scale, because a running total outgrows its inputs
min(x), max(x)anythe argument’s type

The simulator stores exactly these types and a test asserts it, so state inference and execution cannot drift. The scale is yours: declare amount: decimal(12,2) and the total is kept to two places.

Why the description is so careful

Two versions of an application must be able to compare what they store, to decide whether the running version’s state can be kept. That comparison is only honest if the description depends on what you wrote and on Streamform’s rules alone, never on how the SQL planner happened to name or widen a column in this release. So the fingerprint uses Streamform’s own names for accumulators and its own vocabulary for types, and its digest is a SHA-256 of that text. See Identity.

The simulator’s state is the same state

streamform test executes the plan with an in-memory keyed store shaped exactly like the fingerprint says: a map from key to accumulators of the recorded types. Nothing is approximated, which is what lets the simulator be the oracle the Flink backend is checked against.