Triggers, evictors and the timer service
Why are timers often larger than the data they guard, and why do they fire in bursts?
A trigger converts a condition into an emit. The condition can be "the watermark passed the end of the window", "100 records arrived", or "10 seconds of processing time elapsed".
Timers are stored state
Mechanically, a trigger is a timer service: a priority queue (a structure that always gives the earliest item first) of entries (timestamp, key, namespace). This queue is itself keyed state. It is checkpointed together with everything else, and fired in bulk when the watermark advances. (The namespace identifies which window the timer belongs to.)
Two consequences:
- Timers are often larger than the data they guard. There is one timer per key per window, at about 50 bytes of key plus queue overhead each. The accumulator it guards might be an 8-byte counter. Worked example: 10 million keys with one open window each: timers cost 10,000,000 × 50 B = 500 MB, the counters only 10,000,000 × 8 B = 80 MB. The bookkeeping is six times bigger than the data.
- A watermark advance triggers a synchronous burst. Every timer that is now due fires at once. A watermark that jumps (an idle source resumes, a replay catches up, a source restarts with old data) fires millions of timers in one go. That produces a latency spike and an output burst downstream. This is a thundering herd (many tasks waking at the same instant and competing for the same resources) generated internally by the engine. It is why replay behaviour differs so sharply from steady-state behaviour. (This is what happened when Tomás restarted the stopped source.)
Evictors keep raw data alive
An evictor decides which elements to drop from a window before or after it is computed (for example "keep only the last 100 records"). That means the raw elements must still exist. An evictor converts an O(1)-per-key incremental aggregate into O(window contents) retention. Any window with an evictor is a raw buffer with an aggregate attached.