Batch & Streaming Pipelines intermediate 8 min read 7 flashcards

Stream Windowing: Tumbling, Sliding and Session

How a windowing function assigns each event on an unbounded stream to finite groups, why the three standard shapes differ in state cost and in whether their boundaries are shared across keys, and how late data makes session windows merge after results have been emitted.

"Active users per five minutes" has at least three answers on the same stream. Fixed, non-overlapping five-minute buckets give one. A five-minute window recomputed every minute gives a smoother series with five times as many points. Grouping each user's events into bursts separated by inactivity gives a third that is not even indexed by the clock. None is wrong. An aggregation over an unbounded stream needs a rule for cutting it into finite pieces, and the rule is part of the metric's definition.

The Dataflow model made the rule explicit. Windowing is two operations: AssignWindows, which maps each element to a set of windows, and MergeWindows, which lets windows combine as data arrives, and together they express fixed, sliding and session windows under one abstraction (Akidau et al., 2015, The Dataflow Model, PVLDB 8(12)). When a window's result is emitted is a separate question, answered by watermarks and triggers (watermarks-and-allowed-lateness), and whose clock \(t\) refers to is covered in event-time-versus-processing-time.

Three assignment functions

Tumbling (fixed) windows of size \(w\) partition time. An event with timestamp \(t\) belongs to exactly one window,

\[W(t) = \big[\,k w,\; (k+1) w\,\big), \qquad k = \lfloor t / w \rfloor.\]

Sliding (hopping) windows have size \(w\) and period \(s < w\). An event belongs to every window whose start \(k s\) satisfies \(k s \le t < k s + w\), which is \(w/s\) windows when \(s\) divides \(w\). Flink's documentation puts it directly: when the slide is smaller than the size, elements are assigned to multiple windows (Apache Flink, Windows).

Session windows are defined per key by a gap \(g\). Two consecutive events \(t_i < t_{i+1}\) for the same key share a session if \(t_{i+1} - t_i < g\), and a session spans \([t_{\text{first}},\; t_{\text{last}} + g)\). Boundaries depend on the data, so no function of \(t\) alone can assign them. Implementations give each event its own proto-window \([t, t+g)\) and merge overlapping ones, which is exactly what Flink describes its session operator doing.

What each costs

Sliding windows multiply work. A one-hour window sliding every minute places each event in \(60\) windows, so per-key state and per-event update cost are roughly sixty times the tumbling equivalent. Incremental aggregates (sum, count, min) keep one accumulator per window; a function that needs all elements buffers every event sixty times over. This is why production feature pipelines approximate long sliding windows with tumbling sub-windows summed at read time, as streaming-aggregations-for-features describes.

Tumbling and sliding windows are aligned: every key's window closes at the same instant. At 12:05:00 every one of ten million keys fires, producing an output burst and a latency spike at each boundary. Session windows are unaligned, closing whenever each key goes quiet, which spreads load but makes "all results for 12:00 to 12:05" a meaningless query.

Late data and merging

Sessions interact badly with late events. Take \(g = 30\) minutes and one user's events at 10:00, 10:20 and 11:05. The first two form \([10{:}00,\; 10{:}50)\), the third opens \([11{:}05,\; 11{:}35)\), and suppose both are emitted. An event stamped 10:45 then arrives late. Its proto-window \([10{:}45,\; 11{:}15)\) overlaps both sessions, so all three merge into \([10{:}00,\; 11{:}35)\).

Two results already sent downstream are now wrong, and a single new one replaces them. The Dataflow paper's refinement modes exist for this. Discarding emits only the delta and cannot express that two old sessions ceased to exist. Accumulating re-emits the merged session but leaves the two stale ones in place. Accumulating and retracting emits the new value along with retractions of the previous ones, the only mode in which a downstream sum stays correct, and the one with the least support in sinks.

The gap itself is contested. Web analytics conventionally uses 30 minutes of inactivity, but the right \(g\) depends on the product, and session counts are sensitive to it: halving \(g\) splits every burst with an internal pause between \(g/2\) and \(g\). Some teams abandon inferred sessions and have clients send explicit session identifiers, trading a statistical definition for one that the client can get wrong.

When it breaks

Calendar windows are not tumbling windows. A "daily" window of 86,400 seconds aligned to the epoch is a UTC day. Local business days need timezone-aware assignment, and daylight-saving transitions produce days of 23 and 25 hours that a fixed \(w\) cannot represent.

Sessions can grow without bound. A bot or a monitoring client that never pauses for \(g\) keeps one session open forever, and its state with it. A maximum session duration is a necessary safety cap, even though it changes the definition.

Empty windows emit nothing. A key with no events in a window produces no output, not a zero. Downstream systems that read a missing row as "no data yet" rather than "count was zero" misreport quiet periods, so dense outputs require explicitly generating the empty windows.

Window choice changes the metric. Switching a dashboard from tumbling to sliding windows raises reported peaks, because a burst that straddled a tumbling boundary is now captured whole in some sliding window. Comparisons across a windowing change are comparisons across definitions.

Check yourself

7 flashcards for this concept

Click a card to reveal the answer.

Drill the whole track