KafkaIQ

5.2 Windowing Concepts & Types

Module Overview

Goal: Centralize all window theory in one place.

What You'll Learn:

  • Why windows exist in stream processing
  • Tumbling windows
  • Hopping windows
  • Sliding windows
  • Session windows
  • Grace periods and late data
  • Window retention

What's NOT Covered Here:

  • Aggregation code (covered in Module 5.3)
  • State store internals (covered in Module 6.1)
  • Retention configuration details (covered in Module 6.1)

Key Insight: Windows provide a way to bound state over time, making unbounded streams manageable.


1. Why Windows Exist

The Unbounded Stream Problem

Problem: How to aggregate an infinite stream?

Stream of events: [e1, e2, e3, e4, ... e∞]

Question: "What's the total?"
Answer: Infinite accumulation → unbounded state → runs out of memory

Solution: Window the data
└─ Break infinite stream into finite chunks
└─ Aggregate within each chunk
└─ State becomes bounded

Real-World Examples

Instead of asking:
❌ "Total clicks ever" (unbounded)

Ask:
✅ "Total clicks in last hour" (bounded by time)
✅ "Total clicks per day" (bounded by time)
✅ "Total clicks per user session" (bounded by inactivity)

Windows make these questions answerable

2. Event Time vs Processing Time

Two Notions of Time

EVENT TIME:
└─ When event actually happened
└─ Embedded in the event itself
└─ Example: order.timestamp = "2024-01-15 10:30:00"

PROCESSING TIME:
└─ When event is processed by Streams app
└─ System time when record consumed
└─ Example: System.currentTimeMillis()

Which to Use?

Event Time (preferred):
✅ Accurate business logic
✅ Replay gives same results
✅ Handles out-of-order events
⚠️ Requires timestamps in data

Processing Time:
✅ Simple (no timestamp needed)
✅ Lower latency
❌ Replay gives different results
❌ Out-of-order events misplaced

Kafka Streams uses EVENT TIME by default

Extracting Event Time

// Tell Kafka Streams how to extract timestamp
stream
    .selectKey(...)
    .groupByKey()
    .windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
    .count();

// Default: uses record timestamp from Kafka
// Can override with custom TimestampExtractor

3. Tumbling Windows

Definition

Tumbling windows are fixed-size, non-overlapping windows.

Time →
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│Window 1│ │Window 2│ │Window 3│ │Window 4│
│ 0-5min │ │ 5-10min│ │10-15min│ │15-20min│
└────────┘ └────────┘ └────────┘ └────────┘

Each event belongs to exactly ONE window
No overlap between windows

Characteristics

TUMBLING WINDOWS:

1. Fixed Size
   └─ All windows same duration

2. Non-Overlapping
   └─ Each event in exactly one window

3. Aligned to Epoch
   └─ Windows start at fixed intervals from epoch

4. Use Case
   └─ Periodic reports, dashboards

Example: 5-Minute Tumbling Windows

Window 1: 10:00 - 10:05
Window 2: 10:05 - 10:10
Window 3: 10:10 - 10:15

Event at 10:03 → Window 1
Event at 10:07 → Window 2
Event at 10:14 → Window 3

Each event goes to exactly one window

Code Example

KStream<String, Long> pageViews = builder.stream("page-views");

KTable<Windowed<String>, Long> windowedCounts = pageViews
    .groupByKey()
    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
    .count();

// Output: count per user per 5-minute window
// Key: Windowed<userId>
// Value: count

4. Hopping Windows

Definition

Hopping windows are fixed-size, overlapping windows with a defined advance interval.

Time →
┌────────┐
│Window 1│   Window size: 10 min
│ 0-10   │   Advance:    5 min
└────────┘
     ┌────────┐
     │Window 2│
     │ 5-15   │
     └────────┘
          ┌────────┐
          │Window 3│
          │10-20   │
          └────────┘

Windows overlap
Each event can be in multiple windows

Characteristics

HOPPING WINDOWS:

1. Fixed Size
   └─ Window duration constant

2. Fixed Advance
   └─ New window starts every advance interval

3. Overlapping
   └─ Events can be in multiple windows

4. Use Case
   └─ Moving averages, sliding statistics

Example: 10-Minute Windows, 5-Minute Hop

Window 1: 10:00 - 10:10
Window 2: 10:05 - 10:15
Window 3: 10:10 - 10:20

Event at 10:07:
├─ In Window 1 (10:00 - 10:10) ✅
├─ In Window 2 (10:05 - 10:15) ✅
└─ Not in Window 3

Event counted in MULTIPLE windows

Code Example

KStream<String, Long> pageViews = builder.stream("page-views");

KTable<Windowed<String>, Long> windowedCounts = pageViews
    .groupByKey()
    .windowedBy(TimeWindows
        .ofSizeWithNoGrace(Duration.ofMinutes(10))
        .advanceBy(Duration.ofMinutes(5)))
    .count();

// Output: count per user per window
// Windows overlap by 5 minutes

Hopping vs Tumbling

Tumbling: advanceBy = size
├─ No overlap
└─ Each event in one window

Hopping: advanceBy < size
├─ Overlap
└─ Each event in multiple windows

Special case: advanceBy = size → becomes tumbling

5. Sliding Windows

Definition

Sliding windows are continuous windows that slide with each event.

Time →
Events:    E1      E2        E3
           ↓       ↓         ↓
Window:  [---- 5 min ----]
                 [---- 5 min ----]
                          [---- 5 min ----]

Window moves with each new event
No fixed boundaries

Characteristics

SLIDING WINDOWS:

1. Time Difference-Based
   └─ Window = all events within time diff

2. No Fixed Boundaries
   └─ Windows slide continuously

3. Event-Driven
   └─ New window for each event

4. Use Case
   └─ Correlating events (joins, pattern detection)

Example: 5-Minute Sliding Window

Events:
10:00 - Event A
10:02 - Event B
10:04 - Event C
10:07 - Event D

At 10:04 (Event C):
Window includes events from 9:59 - 10:04
└─ Event A (10:00) ✅
└─ Event B (10:02) ✅
└─ Event C (10:04) ✅

At 10:07 (Event D):
Window includes events from 10:02 - 10:07
└─ Event A (10:00) ❌ (too old)
└─ Event B (10:02) ✅
└─ Event C (10:04) ✅
└─ Event D (10:07) ✅

Code Example

// Used primarily for joins
KStream<String, PageView> views = builder.stream("page-views");
KStream<String, AdClick> clicks = builder.stream("ad-clicks");

// Join views with clicks within 5-minute window
KStream<String, ViewWithClick> joined = views.join(
    clicks,
    (view, click) -> new ViewWithClick(view, click),
    JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5))
);

// Covered in Module 8.1

6. Session Windows

Definition

Session windows are dynamic windows based on activity and inactivity gaps.

Time →
Events:  E1  E2    E3              E4  E5
         ↓   ↓     ↓               ↓   ↓
         [---Session 1---]  gap   [Session 2]
                          (5 min)

Window closes after inactivity gap
New activity starts new session

Characteristics

SESSION WINDOWS:

1. Dynamic Duration
   └─ Window size varies based on activity

2. Inactivity Gap
   └─ Session ends after gap with no events

3. Per-Key Sessions
   └─ Each key has own sessions

4. Use Case
   └─ User sessions, clickstreams, batch jobs

Example: 5-Minute Inactivity Gap

User Activity:
10:00 - Click 1
10:02 - Click 2
10:03 - Click 3
10:15 - Click 4  ← 12 min gap!

Sessions:
Session 1: 10:00 - 10:03
├─ Click 1, Click 2, Click 3
└─ Ended: no activity for 5+ minutes

Session 2: 10:15 - ...
├─ Click 4
└─ Still active (or ends after 5 min inactivity)

Code Example

KStream<String, Event> events = builder.stream("user-events");

KTable<Windowed<String>, Long> sessionCounts = events
    .groupByKey()
    .windowedBy(SessionWindows.ofInactivityGapWithNoGrace(
        Duration.ofMinutes(5)))
    .count();

// Output: count of events per session
// Session closes after 5 minutes of inactivity

Session Merging

Important: Sessions can MERGE

Initial:
Session A: 10:00 - 10:03
Session B: 10:10 - 10:15

Late event at 10:05 arrives:
├─ Falls within gap of both sessions
└─ Sessions merge!

Merged Session: 10:00 - 10:15
└─ All events from A + B + late event

7. Grace Periods and Late Data

The Late Data Problem

Event Time vs Processing Time mismatch:

Event A: timestamp=10:00, arrives at 10:01 ✅ On time
Event B: timestamp=10:00, arrives at 10:20 ⚠️ Late!

Question: Should Event B be included in 10:00's window?

Grace Period

Grace period is extra time to wait for late events before finalizing window.

Window: 10:00 - 10:05
Grace period: 2 minutes

Window stays open until: 10:07

Events arriving before 10:07:
├─ Timestamp between 10:00 - 10:05 ✅ Included

Events arriving after 10:07:
├─ Timestamp between 10:00 - 10:05 ❌ Too late (dropped)

Configuring Grace Period

// With grace period (2 minutes)
TimeWindows.ofSizeAndGrace(
    Duration.ofMinutes(5),
    Duration.ofMinutes(2)
);

// No grace period (strict)
TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5));

Grace Period Trade-offs

LONGER GRACE PERIOD:
✅ Accept more late data
✅ More accurate results
❌ Higher latency (wait longer)
❌ More state (keep windows open)

SHORTER GRACE PERIOD:
✅ Lower latency (results sooner)
✅ Less state
❌ Drop late data
❌ Less accurate results

NO GRACE PERIOD:
✅ Lowest latency
❌ No late data handling

8. Window Comparison

Window Type Size Overlap Boundary Use Case
Tumbling Fixed No Fixed Periodic reports
Hopping Fixed Yes Fixed Moving averages
Sliding Fixed Continuous Event-driven Joins, correlation
Session Dynamic No Activity-based User sessions

JoinWindows vs TimeWindows (Important Distinction)

Critical: For stream-to-stream joins, you MUST use JoinWindows, NOT TimeWindows!

// ❌ WRONG: Can't use TimeWindows for stream joins
ordersStream.join(
    paymentsStream,
    joiner,
    TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5))  // ❌ Won't compile!
);

// ✅ CORRECT: Use JoinWindows for stream joins
ordersStream.join(
    paymentsStream,
    joiner,
    JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5))  // ✅ Correct
);

Why the difference?

TimeWindows (for aggregations):
├─ Groups events by time buckets
├─ Used with: count(), reduce(), aggregate()
└─ Example: "Count events every 5 minutes"

JoinWindows (for stream joins):
├─ Defines time difference tolerance for matching events
├─ Used with: join(), leftJoin(), outerJoin()
└─ Example: "Join events within 5 minutes of each other"

Key Differences:

Feature TimeWindows JoinWindows
Purpose Aggregation bucketing Join matching window
Used with groupBy().windowedBy(TimeWindows) stream.join(stream, JoinWindows)
Semantics "Group events into buckets" "Match events within time range"
Window type Tumbling, Hopping, Sliding Sliding (±time difference)

Example - TimeWindows (Aggregation):

KStream<String, Order> orders = builder.stream("orders");

// Count orders every 5 minutes
orders
    .groupByKey()
    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
    .count();

Example - JoinWindows (Stream Join):

KStream<String, Order> orders = builder.stream("orders");
KStream<String, Payment> payments = builder.stream("payments");

// Join orders with payments within 5-minute window
orders.join(
    payments,
    (order, payment) -> new OrderWithPayment(order, payment),
    JoinWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5))
);

Remember: Aggregations use TimeWindows, Joins use JoinWindows. Mixing them up is a common mistake!


9. Window Retention

Why Retention Matters

Problem: Old window state accumulates

Without retention:
├─ Windows from last month still in state
├─ State grows forever
└─ Eventually runs out of disk

With retention:
├─ Old windows automatically deleted
├─ State remains bounded
└─ Disk usage stable

Default Retention

Default retention = window size + grace period

Example:
Window size:    5 minutes
Grace period:   2 minutes
Retention:      7 minutes

After 7 minutes, window state deleted

Custom Retention

TimeWindows.ofSizeAndGrace(Duration.ofMinutes(5), Duration.ofMinutes(2))
    .until(Duration.ofMinutes(10).toMillis());  // Custom retention

Covered in detail in Module 6.1.


10. Choosing the Right Window Type

Decision Tree

Do you need fixed boundaries?
├─ YES → Tumbling or Hopping
│        │
│        └─ Do you need overlap?
│           ├─ NO → Tumbling
│           └─ YES → Hopping
│
└─ NO → Dynamic windows
         │
         └─ Based on inactivity?
            ├─ YES → Session
            └─ NO → Sliding (for joins)

Use Case Examples

TUMBLING WINDOWS:
✅ Hourly sales reports
✅ Daily user counts
✅ Periodic aggregations

HOPPING WINDOWS:
✅ 10-minute average over last 30 minutes
✅ Moving averages
✅ Overlapping statistics

SLIDING WINDOWS:
✅ Stream-stream joins
✅ Correlation within time window
✅ Event pattern detection

SESSION WINDOWS:
✅ User browsing sessions
✅ Shopping cart sessions
✅ Batch job analysis

11. Window Keys

Windowed Key Structure

Aggregations produce: KTable<Windowed<K>, V>

Windowed<K> contains:
├─ Original key (e.g., userId)
├─ Window start time
└─ Window end time

Example:
Key: Windowed<"user123">
├─ user: "user123"
├─ start: 1704538800000 (10:00)
└─ end:   1704539100000 (10:05)

Extracting Window Information

KTable<Windowed<String>, Long> windowedCounts = /* ... */;

windowedCounts.toStream().foreach((windowedKey, count) -> {
    String user = windowedKey.key();
    long start = windowedKey.window().start();
    long end = windowedKey.window().end();
    
    System.out.printf("User %s: %d events from %s to %s%n",
        user, count, formatTime(start), formatTime(end));
});

12. Common Window Patterns

Pattern 1: Fixed-Interval Reporting

// Count events every 5 minutes
stream
    .groupByKey()
    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(5)))
    .count();

Pattern 2: Moving Average

// 10-minute window, updated every 2 minutes
stream
    .groupByKey()
    .windowedBy(TimeWindows
        .ofSizeWithNoGrace(Duration.ofMinutes(10))
        .advanceBy(Duration.ofMinutes(2)))
    .aggregate(...);

Pattern 3: User Sessions

// Session per user, 30-minute inactivity gap
stream
    .groupByKey()
    .windowedBy(SessionWindows.ofInactivityGapWithNoGrace(
        Duration.ofMinutes(30)))
    .count();

13. Mental Model Summary

Window Types

TUMBLING: [--][--][--][--]
├─ Fixed size, no overlap
└─ Each event in one window

HOPPING:  [----]
            [----]
              [----]
├─ Fixed size, fixed overlap
└─ Each event in multiple windows

SLIDING:  Continuous, event-driven
├─ No fixed boundaries
└─ For joins/correlation

SESSION:  [---] gap [----] gap [--]
├─ Dynamic size
└─ Based on activity/inactivity

Time Concepts

EVENT TIME:
└─ When event happened (in data)
└─ Kafka Streams default

PROCESSING TIME:
└─ When event processed (system time)
└─ Less common, less accurate

GRACE PERIOD:
└─ Extra time to wait for late events
└─ Trade latency for accuracy

14. Questions You Should Be Able to Answer

After this module, you should be able to answer:

✅ Why do we need windows in stream processing?
✅ What's the difference between tumbling and hopping windows?
✅ When would I use session windows?
✅ What is a grace period?
✅ What's the difference between event time and processing time?
✅ How do I choose the right window type?
✅ What happens to late-arriving data?


15. What's Next?

Now that you understand window concepts, the next module will show you how to use them:

  • Module 5.3: Windowed aggregations — combining windows with aggregations
  • Module 6.1: State stores — how windowed state is physically stored
  • Module 8.1: Joins — windowed joins between streams

Summary

You learned:

  • ✅ Windows bound unbounded streams to make state manageable
  • ✅ Tumbling windows: fixed, non-overlapping (periodic reports)
  • ✅ Hopping windows: fixed, overlapping (moving averages)
  • ✅ Sliding windows: continuous, event-driven (joins)
  • ✅ Session windows: dynamic, inactivity-based (user sessions)
  • ✅ Event time (accurate) vs processing time (simple)
  • ✅ Grace periods allow late data with latency trade-off
  • ✅ Window retention prevents unbounded state growth

Next up: Module 5.3 — Windowed Aggregations — Learn how to combine windows with aggregations for time-based analytics.