SysDesignPrep.com
Interactive concepts

Watermarks and late events in stream processing: an interactive simulator

A stream processor that counts events per minute has to decide when a minute is over. Events carry the time they happened, their event time, but reach the processor later and out of order. A watermark is the processor’s running estimate that no event older than some time is still coming. When it passes the end of a window, the window’s result is emitted, and events that turn up after that are late.

The playground plots every event by when it happened against when it arrived. The staircase is the watermark, and each window’s result goes out where the staircase crosses the top of its band.

Watermark delayOffline devicesAllowed lateness

Devices send about 5 events a second, and 5% of them come from devices that were offline and deliver 10 to 40 s late. The processor counts them in 10 s windows by when they happened, and its watermark trails the newest event it has seen by 2 s. So each window's result goes out 2.9 s after the window ends, on average, with 95.5% of its events in it. 13 late events arrive after their window closed and are dropped.

0–1010–2020–3030–4040–5050–60020406080100arrived at the processor (s)happened at (s)happened at 0.1 s, arrived at 0.6 s: in the first resulthappened at 0.6 s, arrived at 1.3 s: in the first resulthappened at 1.1 s, arrived at 1.4 s: in the first resulthappened at 0.9 s, arrived at 1.4 s: in the first resulthappened at 0.2 s, arrived at 2.2 s: in the first resulthappened at 1.6 s, arrived at 2.4 s: in the first resulthappened at 1.9 s, arrived at 2.7 s: in the first resulthappened at 2.0 s, arrived at 2.7 s: in the first resulthappened at 2.5 s, arrived at 3.1 s: in the first resulthappened at 2.6 s, arrived at 3.2 s: in the first resulthappened at 2.5 s, arrived at 3.4 s: in the first resulthappened at 2.0 s, arrived at 3.5 s: in the first resulthappened at 2.7 s, arrived at 3.6 s: in the first resulthappened at 3.5 s, arrived at 3.8 s: in the first resulthappened at 3.5 s, arrived at 4.1 s: in the first resulthappened at 3.8 s, arrived at 4.2 s: in the first resulthappened at 4.1 s, arrived at 4.3 s: in the first resulthappened at 4.1 s, arrived at 4.4 s: in the first resulthappened at 3.5 s, arrived at 4.5 s: in the first resulthappened at 4.2 s, arrived at 4.7 s: in the first resulthappened at 3.0 s, arrived at 4.8 s: in the first resulthappened at 5.7 s, arrived at 6.1 s: in the first resulthappened at 6.0 s, arrived at 6.6 s: in the first resulthappened at 6.1 s, arrived at 6.8 s: in the first resulthappened at 6.4 s, arrived at 7.0 s: in the first resulthappened at 5.7 s, arrived at 7.0 s: in the first resulthappened at 4.9 s, arrived at 7.0 s: in the first resulthappened at 6.4 s, arrived at 7.0 s: in the first resulthappened at 7.1 s, arrived at 7.3 s: in the first resulthappened at 6.6 s, arrived at 7.8 s: in the first resulthappened at 6.5 s, arrived at 7.8 s: in the first resulthappened at 7.3 s, arrived at 7.8 s: in the first resulthappened at 7.5 s, arrived at 7.9 s: in the first resulthappened at 7.9 s, arrived at 8.1 s: in the first resulthappened at 7.7 s, arrived at 8.4 s: in the first resulthappened at 6.4 s, arrived at 8.6 s: in the first resulthappened at 8.9 s, arrived at 9.2 s: in the first resulthappened at 8.3 s, arrived at 9.2 s: in the first resulthappened at 8.5 s, arrived at 9.2 s: in the first resulthappened at 7.0 s, arrived at 9.4 s: in the first resulthappened at 8.1 s, arrived at 9.4 s: in the first resulthappened at 9.0 s, arrived at 9.6 s: in the first resulthappened at 9.0 s, arrived at 10.0 s: in the first resulthappened at 9.7 s, arrived at 10.3 s: in the first resulthappened at 9.9 s, arrived at 10.6 s: in the first resulthappened at 9.6 s, arrived at 10.6 s: in the first resulthappened at 10.8 s, arrived at 11.3 s: in the first resulthappened at 10.9 s, arrived at 11.4 s: in the first resulthappened at 9.9 s, arrived at 11.8 s: in the first resulthappened at 11.5 s, arrived at 12.0 s: in the first resulthappened at 11.1 s, arrived at 12.1 s: in the first resulthappened at 11.2 s, arrived at 12.2 s: in the first resulthappened at 11.1 s, arrived at 12.3 s: in the first resulthappened at 10.7 s, arrived at 12.7 s: in the first resulthappened at 12.1 s, arrived at 13.0 s: in the first resulthappened at 11.6 s, arrived at 13.0 s: in the first resulthappened at 12.7 s, arrived at 13.4 s: in the first resulthappened at 12.3 s, arrived at 13.5 s: in the first resulthappened at 13.3 s, arrived at 13.6 s: in the first resulthappened at 12.4 s, arrived at 13.8 s: in the first resulthappened at 13.6 s, arrived at 13.9 s: in the first resulthappened at 12.4 s, arrived at 14.4 s: in the first resulthappened at 14.1 s, arrived at 15.1 s: in the first resulthappened at 14.3 s, arrived at 15.2 s: in the first resulthappened at 15.0 s, arrived at 15.3 s: in the first resulthappened at 14.9 s, arrived at 15.5 s: in the first resulthappened at 15.4 s, arrived at 15.8 s: in the first resulthappened at 15.5 s, arrived at 16.0 s: in the first resulthappened at 14.5 s, arrived at 16.1 s: in the first resulthappened at 15.4 s, arrived at 16.3 s: in the first resulthappened at 16.4 s, arrived at 16.6 s: in the first resulthappened at 14.5 s, arrived at 16.6 s: in the first resulthappened at 15.2 s, arrived at 16.7 s: in the first resulthappened at 15.9 s, arrived at 16.7 s: in the first resulthappened at 16.3 s, arrived at 16.8 s: in the first resulthappened at 16.7 s, arrived at 16.9 s: in the first resulthappened at 16.7 s, arrived at 16.9 s: in the first resulthappened at 16.3 s, arrived at 17.3 s: in the first resulthappened at 16.6 s, arrived at 17.4 s: in the first resulthappened at 16.1 s, arrived at 18.0 s: in the first resulthappened at 15.9 s, arrived at 18.0 s: in the first resulthappened at 17.8 s, arrived at 18.1 s: in the first resulthappened at 18.3 s, arrived at 18.6 s: in the first resulthappened at 17.7 s, arrived at 18.6 s: in the first resulthappened at 17.5 s, arrived at 18.7 s: in the first resulthappened at 18.8 s, arrived at 19.1 s: in the first resulthappened at 18.5 s, arrived at 19.3 s: in the first resulthappened at 18.7 s, arrived at 19.5 s: in the first resulthappened at 18.9 s, arrived at 19.5 s: in the first resulthappened at 19.9 s, arrived at 20.2 s: in the first resulthappened at 19.6 s, arrived at 20.5 s: in the first resulthappened at 19.9 s, arrived at 20.6 s: in the first resulthappened at 20.3 s, arrived at 20.7 s: in the first resulthappened at 20.3 s, arrived at 20.7 s: in the first resulthappened at 19.3 s, arrived at 21.1 s: in the first resulthappened at 19.8 s, arrived at 21.2 s: in the first resulthappened at 20.1 s, arrived at 21.6 s: in the first resulthappened at 20.2 s, arrived at 21.6 s: in the first resulthappened at 21.6 s, arrived at 21.9 s: in the first resulthappened at 21.4 s, arrived at 22.0 s: in the first resulthappened at 21.7 s, arrived at 22.0 s: in the first resulthappened at 21.3 s, arrived at 22.1 s: in the first resulthappened at 21.4 s, arrived at 22.1 s: in the first resulthappened at 22.4 s, arrived at 22.7 s: in the first resulthappened at 22.0 s, arrived at 22.8 s: in the first resulthappened at 22.7 s, arrived at 23.1 s: in the first resulthappened at 22.2 s, arrived at 23.1 s: in the first resulthappened at 22.4 s, arrived at 23.2 s: in the first resulthappened at 23.1 s, arrived at 23.5 s: in the first resulthappened at 23.0 s, arrived at 23.6 s: in the first resulthappened at 22.8 s, arrived at 23.6 s: in the first resulthappened at 22.4 s, arrived at 23.8 s: in the first resulthappened at 22.4 s, arrived at 24.2 s: in the first resulthappened at 22.6 s, arrived at 24.3 s: in the first resulthappened at 24.2 s, arrived at 25.1 s: in the first resulthappened at 22.8 s, arrived at 25.2 s: in the first resulthappened at 24.8 s, arrived at 25.3 s: in the first resulthappened at 24.2 s, arrived at 25.5 s: in the first resulthappened at 24.9 s, arrived at 25.6 s: in the first resulthappened at 23.5 s, arrived at 25.6 s: in the first resulthappened at 25.4 s, arrived at 25.8 s: in the first resulthappened at 24.0 s, arrived at 25.9 s: in the first resulthappened at 25.2 s, arrived at 26.1 s: in the first resulthappened at 24.9 s, arrived at 26.2 s: in the first resulthappened at 25.5 s, arrived at 26.3 s: in the first resulthappened at 25.8 s, arrived at 26.4 s: in the first resulthappened at 26.3 s, arrived at 26.6 s: in the first resulthappened at 24.4 s, arrived at 27.2 s: in the first resulthappened at 26.7 s, arrived at 27.2 s: in the first resulthappened at 26.5 s, arrived at 27.4 s: in the first resulthappened at 27.4 s, arrived at 27.7 s: in the first resulthappened at 27.0 s, arrived at 28.0 s: in the first resulthappened at 27.7 s, arrived at 28.3 s: in the first resulthappened at 28.1 s, arrived at 28.5 s: in the first resulthappened at 27.9 s, arrived at 28.6 s: in the first resulthappened at 16.5 s, arrived at 28.8 s: late, droppedhappened at 28.0 s, arrived at 28.8 s: in the first resulthappened at 27.7 s, arrived at 28.9 s: in the first resulthappened at 28.0 s, arrived at 28.9 s: in the first resulthappened at 28.6 s, arrived at 29.0 s: in the first resulthappened at 27.0 s, arrived at 29.1 s: in the first resulthappened at 27.5 s, arrived at 29.2 s: in the first resulthappened at 29.2 s, arrived at 29.5 s: in the first resulthappened at 29.4 s, arrived at 30.0 s: in the first resulthappened at 28.8 s, arrived at 30.0 s: in the first resulthappened at 28.3 s, arrived at 30.5 s: in the first resulthappened at 29.3 s, arrived at 30.5 s: in the first resulthappened at 26.5 s, arrived at 30.7 s: in the first resulthappened at 30.0 s, arrived at 31.2 s: in the first resulthappened at 31.1 s, arrived at 31.5 s: in the first resulthappened at 31.0 s, arrived at 31.6 s: in the first resulthappened at 31.1 s, arrived at 31.6 s: in the first resulthappened at 30.6 s, arrived at 31.7 s: in the first resulthappened at 31.2 s, arrived at 31.8 s: in the first resulthappened at 30.6 s, arrived at 31.9 s: in the first resulthappened at 29.7 s, arrived at 32.0 s: in the first resulthappened at 31.6 s, arrived at 32.0 s: in the first resulthappened at 30.7 s, arrived at 32.3 s: in the first resulthappened at 32.6 s, arrived at 33.0 s: in the first resulthappened at 31.8 s, arrived at 33.0 s: in the first resulthappened at 32.7 s, arrived at 33.1 s: in the first resulthappened at 32.8 s, arrived at 33.2 s: in the first resulthappened at 32.8 s, arrived at 33.3 s: in the first resulthappened at 32.8 s, arrived at 33.5 s: in the first resulthappened at 33.3 s, arrived at 33.7 s: in the first resulthappened at 32.4 s, arrived at 33.8 s: in the first resulthappened at 33.9 s, arrived at 34.2 s: in the first resulthappened at 33.8 s, arrived at 34.2 s: in the first resulthappened at 33.4 s, arrived at 34.4 s: in the first resulthappened at 34.0 s, arrived at 34.8 s: in the first resulthappened at 23.9 s, arrived at 35.1 s: late, droppedhappened at 35.0 s, arrived at 35.2 s: in the first resulthappened at 34.8 s, arrived at 35.4 s: in the first resulthappened at 34.7 s, arrived at 35.6 s: in the first resulthappened at 35.5 s, arrived at 35.7 s: in the first resulthappened at 34.6 s, arrived at 35.8 s: in the first resulthappened at 35.5 s, arrived at 36.1 s: in the first resulthappened at 35.7 s, arrived at 36.6 s: in the first resulthappened at 36.4 s, arrived at 36.6 s: in the first resulthappened at 35.4 s, arrived at 36.7 s: in the first resulthappened at 35.1 s, arrived at 36.7 s: in the first resulthappened at 36.0 s, arrived at 36.9 s: in the first resulthappened at 36.4 s, arrived at 36.9 s: in the first resulthappened at 34.4 s, arrived at 37.6 s: in the first resulthappened at 7.1 s, arrived at 37.6 s: late, droppedhappened at 37.1 s, arrived at 38.0 s: in the first resulthappened at 37.1 s, arrived at 38.1 s: in the first resulthappened at 37.6 s, arrived at 38.1 s: in the first resulthappened at 38.0 s, arrived at 38.2 s: in the first resulthappened at 37.4 s, arrived at 38.5 s: in the first resulthappened at 37.5 s, arrived at 38.6 s: in the first resulthappened at 37.6 s, arrived at 39.0 s: in the first resulthappened at 38.2 s, arrived at 39.2 s: in the first resulthappened at 38.5 s, arrived at 39.2 s: in the first resulthappened at 37.5 s, arrived at 39.3 s: in the first resulthappened at 37.6 s, arrived at 39.3 s: in the first resulthappened at 39.0 s, arrived at 39.5 s: in the first resulthappened at 37.6 s, arrived at 39.6 s: in the first resulthappened at 39.3 s, arrived at 39.8 s: in the first resulthappened at 38.8 s, arrived at 39.8 s: in the first resulthappened at 39.7 s, arrived at 40.0 s: in the first resulthappened at 38.2 s, arrived at 40.1 s: in the first resulthappened at 36.1 s, arrived at 40.2 s: in the first resulthappened at 39.7 s, arrived at 40.3 s: in the first resulthappened at 40.3 s, arrived at 40.6 s: in the first resulthappened at 40.1 s, arrived at 40.7 s: in the first resulthappened at 39.5 s, arrived at 40.8 s: in the first resulthappened at 40.3 s, arrived at 40.8 s: in the first resulthappened at 40.3 s, arrived at 40.8 s: in the first resulthappened at 40.7 s, arrived at 41.0 s: in the first resulthappened at 40.6 s, arrived at 41.1 s: in the first resulthappened at 40.9 s, arrived at 42.0 s: in the first resulthappened at 41.8 s, arrived at 42.1 s: in the first resulthappened at 41.6 s, arrived at 42.4 s: in the first resulthappened at 41.4 s, arrived at 42.7 s: in the first resulthappened at 41.2 s, arrived at 43.0 s: in the first resulthappened at 42.4 s, arrived at 43.0 s: in the first resulthappened at 43.0 s, arrived at 43.5 s: in the first resulthappened at 43.5 s, arrived at 44.3 s: in the first resulthappened at 44.3 s, arrived at 44.6 s: in the first resulthappened at 44.2 s, arrived at 44.7 s: in the first resulthappened at 43.7 s, arrived at 44.7 s: in the first resulthappened at 44.8 s, arrived at 45.3 s: in the first resulthappened at 44.3 s, arrived at 45.5 s: in the first resulthappened at 45.1 s, arrived at 45.7 s: in the first resulthappened at 45.0 s, arrived at 45.8 s: in the first resulthappened at 43.2 s, arrived at 45.8 s: in the first resulthappened at 45.8 s, arrived at 46.2 s: in the first resulthappened at 45.5 s, arrived at 46.4 s: in the first resulthappened at 46.3 s, arrived at 46.7 s: in the first resulthappened at 46.3 s, arrived at 46.8 s: in the first resulthappened at 46.6 s, arrived at 47.4 s: in the first resulthappened at 47.0 s, arrived at 47.6 s: in the first resulthappened at 47.5 s, arrived at 47.9 s: in the first resulthappened at 46.0 s, arrived at 48.4 s: in the first resulthappened at 48.4 s, arrived at 48.7 s: in the first resulthappened at 48.2 s, arrived at 48.7 s: in the first resulthappened at 47.8 s, arrived at 49.0 s: in the first resulthappened at 48.3 s, arrived at 49.0 s: in the first resulthappened at 47.0 s, arrived at 49.2 s: in the first resulthappened at 48.0 s, arrived at 49.3 s: in the first resulthappened at 47.6 s, arrived at 49.6 s: in the first resulthappened at 48.4 s, arrived at 49.7 s: in the first resulthappened at 19.5 s, arrived at 49.7 s: late, droppedhappened at 48.3 s, arrived at 50.0 s: in the first resulthappened at 49.8 s, arrived at 50.5 s: in the first resulthappened at 50.5 s, arrived at 50.8 s: in the first resulthappened at 51.2 s, arrived at 51.8 s: in the first resulthappened at 51.7 s, arrived at 52.3 s: in the first resulthappened at 50.8 s, arrived at 52.3 s: in the first resulthappened at 51.4 s, arrived at 52.6 s: in the first resulthappened at 52.7 s, arrived at 53.0 s: in the first resulthappened at 52.1 s, arrived at 53.1 s: in the first resulthappened at 49.8 s, arrived at 53.2 s: late, droppedhappened at 50.6 s, arrived at 53.4 s: in the first resulthappened at 53.0 s, arrived at 53.6 s: in the first resulthappened at 53.5 s, arrived at 54.1 s: in the first resulthappened at 54.0 s, arrived at 54.7 s: in the first resulthappened at 54.2 s, arrived at 54.7 s: in the first resulthappened at 53.4 s, arrived at 54.9 s: in the first resulthappened at 54.5 s, arrived at 55.5 s: in the first resulthappened at 55.0 s, arrived at 55.8 s: in the first resulthappened at 55.0 s, arrived at 56.0 s: in the first resulthappened at 55.6 s, arrived at 56.1 s: in the first resulthappened at 55.1 s, arrived at 56.2 s: in the first resulthappened at 55.4 s, arrived at 56.3 s: in the first resulthappened at 40.8 s, arrived at 56.4 s: late, droppedhappened at 56.6 s, arrived at 57.0 s: in the first resulthappened at 53.4 s, arrived at 57.0 s: in the first resulthappened at 56.5 s, arrived at 57.2 s: in the first resulthappened at 56.5 s, arrived at 57.2 s: in the first resulthappened at 58.1 s, arrived at 58.8 s: in the first resulthappened at 57.9 s, arrived at 58.9 s: in the first resulthappened at 57.8 s, arrived at 59.2 s: in the first resulthappened at 58.9 s, arrived at 59.3 s: in the first resulthappened at 59.1 s, arrived at 59.3 s: in the first resulthappened at 58.7 s, arrived at 59.5 s: in the first resulthappened at 58.6 s, arrived at 59.6 s: in the first resulthappened at 58.4 s, arrived at 59.6 s: in the first resulthappened at 59.4 s, arrived at 59.9 s: in the first resulthappened at 57.7 s, arrived at 60.2 s: in the first resulthappened at 58.4 s, arrived at 60.4 s: in the first resulthappened at 58.8 s, arrived at 60.9 s: in the first resulthappened at 59.1 s, arrived at 61.0 s: in the first resulthappened at 25.7 s, arrived at 63.5 s: late, droppedhappened at 43.8 s, arrived at 63.8 s: late, droppedhappened at 51.4 s, arrived at 66.9 s: late, droppedhappened at 41.1 s, arrived at 69.3 s: late, droppedhappened at 43.6 s, arrived at 75.7 s: late, droppedhappened at 55.2 s, arrived at 87.8 s: late, droppedhappened at 54.4 s, arrived at 88.5 s: late, dropped47/4847/4954/5654/5440/4536/39
Wait for a result2.9 safter the window ends
In the first result95.5%of each window’s events
Updates sent0late events within allowed lateness
Dropped as late13arrived after the window was final
in the first resultlate, updates the resultlate, droppedwatermarkresult sent: counted/actual

Completeness against latency

A watermark that trails the newest event by 2 seconds sends each window’s result 2.9 seconds after the window ends, with 95.5% of its events; the 13 events that missed their window are dropped, almost all from devices that were offline. With no delay, results come in under a second but 20 events are dropped. Waiting 15 seconds still drops 7, because some devices were offline for longer than that, and now every result waits 15 seconds for them. No fixed delay is right for every event: the delay sets how long the common case waits, not whether stragglers are caught.

Allowed lateness: send early, then correct

The better answer is to emit on time and fix it later. With allowed lateness, the processor keeps a window’s state for a while after the watermark passes it, and each late event updates the result: with a 2 second watermark and 30 seconds of lateness, 12 late events update their windows and only 1, arriving more than 30 seconds after its window closed, is dropped. Whatever reads the results has to handle updates, by upserting the window’s row rather than appending, and events later even than that go to a side output for a batch job to reconcile instead of vanishing.

  • Event time vs processing time. Windowing by arrival time is simpler and never late, but a device that reconnects after an hour dumps an hour of events into the current minute.
  • Where watermarks come from. From the source, such as per Kafka partition, with the operator’s watermark the minimum across its inputs. One idle partition holds every window open unless it is marked idle.
  • Other windows. Sliding windows overlap, so each event lands in several; session windows close after a gap in activity, so they too wait for the watermark to pass the gap.

Watermarks and lateness in code

// Flink: count clicks per page in 10 second windows of event time
DataStream<PageCount> counts = clicks
    .assignTimestampsAndWatermarks(
        WatermarkStrategy.<Click>forBoundedOutOfOrderness(Duration.ofSeconds(2))  // watermark = newest - 2 s
            .withTimestampAssigner((click, ts) -> click.eventTime))
    .keyBy(click -> click.page)
    .window(TumblingEventTimeWindows.of(Duration.ofSeconds(10)))
    .allowedLateness(Duration.ofSeconds(30))   // late clicks update the result
    .sideOutputLateData(tooLate)               // later still: set aside, not silently lost
    .aggregate(new CountClicks());

Kafka Streams calls allowed lateness a grace period, and Spark Structured Streaming uses a single withWatermark delay both to decide when to emit, in append mode, and how long to accept late data.

In a system design interview

When you aggregate a stream, say which clock the windows use and what happens to late data: “Ad clicks are counted per minute by event time. The watermark trails by 10 seconds, so dashboards update within about a minute and ten seconds. Clicks up to an hour late update the minute’s row in the sink; anything later goes to a late-events topic, and the nightly batch job over raw clicks recomputes billing totals exactly.” Expect the follow-ups: duplicates when a late update is retried, how the sink handles updates, and how far the real-time numbers may drift from the batch ones.

Read the guide to windowing and watermarks or batch and stream processing, see Kafka partitions for where the events come from, or watch a strong candidate design an ad click aggregator.

Questions people ask

What is a watermark in stream processing?

A watermark is a timestamp flowing through the stream that asserts no events with an earlier event time are still expected. When the watermark passes the end of a window, the processor treats the window as complete and emits its result. A common heuristic sets it to the newest event time seen minus a fixed delay.

What is the difference between event time and processing time?

Event time is when something happened, recorded by the device or service that produced the event. Processing time is when the stream processor handles it. They differ by network delay, buffering and outages, and windows by event time give correct counts even when events arrive late or out of order.

What happens to late events?

An event is late when it arrives after the watermark has passed the end of its window. Depending on configuration it is dropped, it updates the already emitted result if it falls within the allowed lateness, or it is sent to a side output so it can be reconciled separately.

How do you choose the watermark delay?

From the distribution of delays: long enough that the common case is complete, short enough that results are timely. A longer delay makes every result wait and still cannot cover devices that were offline for minutes, so pair a modest delay with allowed lateness and updates for the stragglers.

What is allowed lateness?

How long a window’s state is kept after the watermark passes it. Late events within that time update the window’s result, which is emitted again, so the sink must upsert results rather than append them. Kafka Streams calls this the grace period.

Why does an idle partition stall the watermark?

An operator’s watermark is the minimum of the watermarks of its inputs, so a Kafka partition that receives no events keeps the minimum where it was and no window can close. Stream processors let you mark sources idle after a timeout so they stop holding back the watermark.

Should windows use event time or processing time?

Use event time when results must reflect when things happened, as for billing, analytics or anything reprocessed from history. Processing time is simpler and never late, which suits monitoring the pipeline itself, but bursts of delayed events land in the wrong windows.