10.6.3 – Aggregations & Windows
Module: Section 10 – Kafka Ecosystem & Tools
Section: 10.6 – Querying & Processing with ksqlDB
Goal: Master aggregations and windowing in ksqlDB
Aggregation Functions
SELECT
user_id,
COUNT(*) AS click_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
MIN(amount) AS min_amount,
MAX(amount) AS max_amount
FROM user_clicks
GROUP BY user_id
EMIT CHANGES;
Tumbling Windows
Fixed-size, non-overlapping windows
SELECT
user_id,
COUNT(*) AS clicks_per_hour,
WINDOWSTART AS window_start,
WINDOWEND AS window_end
FROM user_clicks
WINDOW TUMBLING (SIZE 1 HOUR)
GROUP BY user_id
EMIT CHANGES;
Hopping Windows
Fixed-size, overlapping windows
SELECT
user_id,
COUNT(*) AS clicks
FROM user_clicks
WINDOW HOPPING (SIZE 1 HOUR, ADVANCE BY 15 MINUTES)
GROUP BY user_id
EMIT CHANGES;
Session Windows
Activity-based windows
SELECT
user_id,
COUNT(*) AS actions_in_session
FROM user_actions
WINDOW SESSION (30 MINUTES)
GROUP BY user_id
EMIT CHANGES;
Advanced Windowing Techniques
Session Windows with Inactivity Gaps
Session windows track activity sessions with dynamic window boundaries based on user inactivity:
CREATE TABLE user_sessions AS
SELECT
user_id,
COUNT(*) AS event_count,
COLLECT_LIST(event_type) AS events,
WINDOWSTART AS session_start,
WINDOWEND AS session_end,
(WINDOWEND - WINDOWSTART) / 1000 AS session_duration_seconds
FROM user_events
WINDOW SESSION (30 MINUTES)
GROUP BY user_id
EMIT CHANGES;
Key Features:
- Window closes after 30 minutes of inactivity
- Automatically adapts to user behavior
- Each user can have multiple concurrent sessions
- Sessions merge if events arrive within gap period
Example Flow:
User clicks at:
10:00:00 → Session starts
10:05:00 → Session extends
10:15:00 → Session extends
[30 minute gap - no events]
10:45:00 → Session closes at 10:15:00
10:45:00 → New session starts
Use Cases:
- ✅ Clickstream analysis
- ✅ User engagement tracking
- ✅ Shopping cart abandonment detection
- ✅ Application usage patterns
Grace Periods for Late-Arriving Data
Handle out-of-order events in windowed aggregations:
SELECT
product_id,
SUM(quantity) AS total_sold,
WINDOWSTART AS hour_start
FROM orders
WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD 15 MINUTES)
GROUP BY product_id
EMIT CHANGES;
How It Works:
- Window closes at normal time (e.g., 11:00)
- Grace period extends window state for 15 minutes
- Late events arriving between 11:00-11:15 still update the window
- After 11:15, window is finalized and late events ignored
Example Timeline:
Window: 10:00 - 11:00
Grace Period: 11:00 - 11:15
Event arrives at 10:55 with timestamp 10:30 → ✅ Accepted
Event arrives at 11:10 with timestamp 10:45 → ✅ Accepted (within grace)
Event arrives at 11:20 with timestamp 10:50 → ❌ Ignored (past grace)
Key Benefits:
- ✅ Accepts late-arriving data up to grace period past window close
- ✅ Maintains accurate aggregates despite network delays
- ✅ Configurable per query based on latency requirements
- ✅ Balances accuracy vs. result freshness
Configuration Examples:
-- Short grace period (low latency systems)
WINDOW TUMBLING (SIZE 5 MINUTES, GRACE PERIOD 1 MINUTE)
-- Medium grace period (typical distributed systems)
WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD 15 MINUTES)
-- Long grace period (high latency or multi-region)
WINDOW TUMBLING (SIZE 1 DAY, GRACE PERIOD 4 HOURS)
Window Retention Tuning
Control how long window state is retained in RocksDB:
-- Set window retention to 12 hours
SET 'ksql.streams.window.store.retention.ms' = '43200000';
-- Now create windowed aggregation
CREATE TABLE hourly_sales WITH (
KAFKA_TOPIC='hourly_sales',
VALUE_FORMAT='AVRO'
) AS
SELECT
store_id,
SUM(amount) AS hourly_revenue,
COUNT(*) AS transaction_count,
WINDOWSTART AS hour
FROM transactions
WINDOW TUMBLING (SIZE 1 HOUR, GRACE PERIOD 30 MINUTES)
GROUP BY store_id
EMIT CHANGES;
Retention Calculation:
Retention = Window Size + Grace Period + Buffer
Example:
Window: 1 hour (3,600,000 ms)
Grace: 30 minutes (1,800,000 ms)
Buffer: 10 hours (36,000,000 ms) -- Extra safety margin
Total: 12 hours (43,200,000 ms)
Use Cases:
Extended Fraud Detection:
SET 'ksql.streams.window.store.retention.ms' = '604800000'; -- 7 days CREATE TABLE suspicious_patterns AS SELECT user_id, COUNT(*) AS failed_logins FROM login_attempts WINDOW TUMBLING (SIZE 1 DAY, GRACE PERIOD 2 HOURS) WHERE status = 'FAILED' GROUP BY user_id HAVING COUNT(*) > 10;Multi-Day Trend Analysis:
SET 'ksql.streams.window.store.retention.ms' = '2592000000'; -- 30 days CREATE TABLE monthly_user_activity AS SELECT user_id, COUNT(*) AS daily_actions FROM user_events WINDOW TUMBLING (SIZE 1 DAY) GROUP BY user_id;Compliance Scenarios:
- Retain window state for audit periods
- Support regulatory reporting windows
- Historical data reconstruction
Window Metadata Access
Use WINDOWSTART and WINDOWEND for time-aware processing:
SELECT
user_id,
COUNT(*) AS event_count,
WINDOWSTART AS window_start_ms,
WINDOWEND AS window_end_ms,
FROM_UNIXTIME(WINDOWSTART) AS window_start_readable,
FROM_UNIXTIME(WINDOWEND) AS window_end_readable,
(WINDOWEND - WINDOWSTART) / 1000 AS window_size_seconds
FROM user_events
WINDOW TUMBLING (SIZE 1 HOUR)
GROUP BY user_id
EMIT CHANGES;
Result:
+----------+-------------+------------------+----------------+-------------------------+-----------------------+---------------------+
| USER_ID | EVENT_COUNT | WINDOW_START_MS | WINDOW_END_MS | WINDOW_START_READABLE | WINDOW_END_READABLE | WINDOW_SIZE_SECONDS |
+----------+-------------+------------------+----------------+-------------------------+-----------------------+---------------------+
| user123 | 45 | 1738407600000 | 1738411200000 | 2025-02-01 10:00:00 | 2025-02-01 11:00:00 | 3600 |
+----------+-------------+------------------+----------------+-------------------------+-----------------------+---------------------+
Advanced Window Patterns
Pattern 1: Nested Aggregations
-- Calculate hourly averages, then daily maximums
CREATE TABLE hourly_temps AS
SELECT sensor_id, AVG(temperature) AS avg_temp, WINDOWSTART AS hour
FROM sensor_readings
WINDOW TUMBLING (SIZE 1 HOUR)
GROUP BY sensor_id;
CREATE TABLE daily_max_temps AS
SELECT sensor_id, MAX(avg_temp) AS max_hourly_avg
FROM hourly_temps
WINDOW TUMBLING (SIZE 1 DAY)
GROUP BY sensor_id;
Pattern 2: Conditional Windowing
-- Count high-value transactions per hour
SELECT
merchant_id,
COUNT(*) AS high_value_count,
SUM(amount) AS total_amount
FROM transactions
WHERE amount > 1000 -- Filter before windowing
WINDOW TUMBLING (SIZE 1 HOUR)
GROUP BY merchant_id
HAVING COUNT(*) > 10 -- Filter after aggregation
EMIT CHANGES;
Pattern 3: Multi-Level Windows
-- 5-minute windows with 1-hour monitoring
SELECT
region,
COUNT(*) AS request_count,
WINDOWSTART AS five_min_window
FROM api_requests
WINDOW TUMBLING (SIZE 5 MINUTES, GRACE PERIOD 1 MINUTE)
GROUP BY region
EMIT CHANGES;
Best Practices for Windowing
1. Match Grace Periods to Expected Latency
| System Type | Typical Grace Period | Rationale |
|---|---|---|
| Real-time IoT | 1-5 minutes | Low latency, local processing |
| Web Applications | 5-15 minutes | Network delays, caching |
| Batch Integrations | 1-4 hours | Scheduled data loads |
| Multi-Region | 4-24 hours | Geographic distribution, disaster recovery |
2. Monitor State Store Memory Usage
-- Check state store sizes
DESCRIBE EXTENDED user_sessions;
-- Look for "Local state store names" and "Storage directory"
-- Monitor RocksDB metrics via JMX
Warning Signs:
- State store size growing unbounded
- Out of memory errors
- Slow aggregation queries
- High disk I/O
Solutions:
- ✅ Reduce window retention
- ✅ Increase JVM heap size
- ✅ Use shorter window sizes
- ✅ Archive old windows to external storage
3. Use WINDOWSTART/WINDOWEND Metadata
Always include window boundaries for:
- Debugging and troubleshooting
- Downstream time-based processing
- Result verification
- Time-series visualization
-- ✅ Good: Include window metadata
SELECT user_id, COUNT(*), WINDOWSTART, WINDOWEND ...
-- ❌ Bad: Missing window context
SELECT user_id, COUNT(*) ...
4. Consider Retention Requirements
Before setting retention:
Ask:
- How long do consumers need to query historical windows?
- What's the maximum acceptable late event delay?
- Are there regulatory/compliance requirements?
- What's the available disk space?
Example Decision Tree:
Real-time dashboard (last hour only):
→ Retention = 2 hours (window + grace + small buffer)
Fraud detection (7-day patterns):
→ Retention = 10 days (7 days + grace + buffer)
Compliance audit (30-day windows):
→ Retention = 35 days minimum
5. Test with Out-of-Order Data
Always test window behavior with:
- Late-arriving events
- Duplicate events
- Events with future timestamps
- Network partition scenarios
-- Insert test events with varying timestamps
INSERT INTO test_stream (user_id, event_time, event_type) VALUES
('u1', 1738407600000, 'click'), -- On time
('u1', 1738407300000, 'click'), -- 5 min late
('u1', 1738411200000, 'click'), -- Future timestamp
('u1', 1738407600000, 'click'); -- Duplicate
Key Takeaways
- COUNT, SUM, AVG, MIN, MAX: Standard aggregations
- TUMBLING: Fixed, non-overlapping windows
- HOPPING: Fixed, overlapping windows
- SESSION: Activity-based windows
- GROUP BY: Required for aggregations
What's Next?
Next Module: 10.6.4 – Stateful Processing in ksqlDB