Skip to content
About
Main content

Windows are retention rules in time

When are sliding windows cheap, and why do session windows need a maximum length?

An endless stream needs a bounded rule for what to remember. Windows express that rule in time. A window groups records into finite pieces so that an aggregate can be computed and emitted.

Tumbling windows: the cheap case

A cuts time into disjoint buckets of equal size (for example 10 minutes: 12:00–12:10, 12:10–12:20, …). Each record belongs to exactly one bucket. State is proportional to keys × open windows.

Sliding windows and the S/s blowup

A sliding window of size S and slide s moves forward every s units, so windows overlap. Each record falls into windows.

Pane decomposition fixes this.

  1. Cut time into small of length s (here, 1 minute).
  2. Compute a partial aggregate per pane. Each record is folded into exactly one pane, so per-record work returns to O(1).
  3. At emit time, combine the S/s panes that make up the window (here, 1,440 partial counts).

State becomes keys × S/s pane accumulators (small ones, like an 8-byte counter), not a copy of each record in 1,440 windows.

# 1-minute panes; window = last 1440 panes
pane_counts[(restaurant, minute_of(event_time))] += 1       # O(1) per record
def emit(restaurant, now_minute):                           # once per minute
    return sum(pane_counts[(restaurant, m)] for m in range(now_minute - 1439, now_minute + 1))

, because partial results must be combinable. Exact distinct counts and medians are not decomposable: you cannot get the exact number of distinct users in 24 hours from 1,440 per-minute distinct counts, since the same user may appear in many panes. This is where enter. (approximate distinct count) and (approximate quantiles, such as the median) are , which is exactly what pane decomposition requires. The same structural demand made them useful in distributed aggregation, appearing again.

Invertible aggregates admit an even cheaper form: keep a single running value, add the pane that enters the window and subtract the pane that leaves. But floating-point sums are not safely . Addition of floating-point numbers is not under rounding, so subtracting a value does not exactly cancel what was added.

Small example (in 64-bit floating point): start with 1e16, add 1, then subtract 1e16. The exact answer is 1. The computer gives 0, because 1e16 + 1 is not representable and rounds back to 1e16. A running window sum that adds and subtracts such values can drift without any error being reported.

Session windows: boundaries defined by absence

A groups the records of one key that are close together in time. A session ends when there has been no data for a gap period (for example 30 minutes of no activity). That makes session windows categorically different:

  • The boundary is defined by the absence of data, so windows are created per record and must merge when a later record bridges two existing sessions. Merging requires the per-window accumulator to be mergeable, which limits what you can store.
  • There is no a priori bound on a session's length. A key that never goes quiet never closes, and its state grows without limit. A bot sending an event every 10 seconds forever is one session forever.
  • The only real defence is a maximum session duration.

Global window with a custom trigger

The puts all records of a key into one window that never ends, and a custom decides when to emit. This is "write your own ", and therefore the place where unbounded state is most often introduced.