Two clocks

Every event carries two clocks. Event time is when something happened on a device; processing time is when the pipeline finally sees it. On a healthy network the two stay close. On a phone that drops into a tunnel, they can drift apart by minutes. A windowed aggregation keyed on event time has to decide when a window is complete, and nothing in the stream tells it for certain.The Dataflow Model paper (Akidau et al., 2015) describes this as a tradeoff between correctness, latency and cost.

A watermark is the operator’s running assertion that no events older than time W are still in flight. The simplest strategy trails the largest timestamp seen so far by a fixed bound:

LST. 01 — JAVA / FLINK
// bound the disorder we are willing to wait for
WatermarkStrategy
    .<ClickEvent>forBoundedOutOfOrderness(Duration.ofSeconds(5))
    .withTimestampAssigner((event, ts) -> event.occurredAt())
    .withIdleness(Duration.ofMinutes(1));

Late data

Anything that arrives with an event time below the current watermark is late. It is dropped, or routed to a side output, depending on configuration. Adjust the bound below to see the trade: a wider bound admits more stragglers, and every window waits that much longer before it fires.

FIG. 01 — WATERMARK BOUND INTERACTIVETAP TO ADJUST
BOUND d = 5.0s
DROPPED 5 / 31 WINDOW FIRES AT 35.0s

Scatter plot of 31 events by arrival time against event time. Raising the bound d drops fewer late events but delays when the 0–30 second window fires.

Events by arrival (x) against event time (y). Filled points arrived before the watermark passed them; ringed points arrived after and are dropped.

No setting of d removes both costs. The rest of this post looks at strategies that move the bound per key and per partition instead of fixing it globally.