Time & Windows
Time is at the heart of stream processing. This guide covers how Laminar handles time and how to use windows effectively. Examples use the sample data.
Why Windows Matter
In batch SQL, you can aggregate over an entire table because it's finite. In streaming, data never stops arriving. Without boundaries, aggregations would never complete.
-- This would never return in streaming
SELECT COUNT(*) FROM orders
-- Instead, use time windows:
SELECT
TUMBLE(interval '1 minute') as window,
COUNT(*) as order_count
FROM orders
GROUP BY window| window_start | window_end | order_count |
|---|---|---|
| 10:00:00 | 10:01:00 | 2 |
| 10:01:00 | 10:02:00 | 2 |
| 10:02:00 | 10:03:00 | 2 |
Event Time vs Processing Time
Event time: When the event actually happened (embedded in the data)
Processing time: When Laminar receives the event
Laminar uses event time by default. This matters because events can arrive late or out of order, and results should be deterministic.
Watermarks
Watermarks answer: "Have all events for this time period arrived?"
When a watermark passes a window boundary, Laminar knows that window is complete and emits the result.
Configuring Late Data Tolerance
CREATE TABLE orders (
order_id VARCHAR,
amount DECIMAL(10,2),
timestamp TIMESTAMP
) WITH (
connector = 'kafka',
...
watermark_delay = '30 seconds'
);Tumbling Windows
Fixed-size, non-overlapping windows. Every event belongs to exactly one window.
SELECT
region,
TUMBLE(interval '1 minute') as window,
COUNT(*) as orders,
SUM(amount) as revenue
FROM orders
GROUP BY region, window| region | window_start | window_end | orders | revenue |
|---|---|---|---|---|
| us-east | 10:00:00 | 10:01:00 | 2 | 350.00 |
| us-west | 10:00:00 | 10:01:00 | 1 | 75.50 |
| us-east | 10:01:00 | 10:02:00 | 1 | 200.00 |
| eu-west | 10:01:00 | 10:02:00 | 1 | 50.00 |
Tumbling with Multiple Dimensions
Group by additional dimensions within each window.
SELECT
region,
status,
TUMBLE(interval '5 minutes') as window,
COUNT(*) as order_count,
SUM(amount) as total_amount
FROM orders
GROUP BY region, status, window| region | status | window_start | window_end | order_count | total_amount |
|---|---|---|---|---|---|
| us-east | completed | 10:00:00 | 10:05:00 | 3 | 364.99 |
| us-east | pending | 10:00:00 | 10:05:00 | 1 | 200.00 |
| us-west | completed | 10:00:00 | 10:05:00 | 2 | 375.50 |
| eu-west | cancelled | 10:00:00 | 10:05:00 | 1 | 50.00 |
Sliding (Hop) Windows
Overlapping windows that slide forward at a regular interval. Events can belong to multiple windows.
-- HOP(slide_interval, window_size)
-- 5-minute window, sliding every 1 minute
SELECT
TUMBLE(interval '1 minute') as window,
HOP(interval '1 minute', interval '5 minutes') as sliding_window,
COUNT(*) as orders,
AVG(amount) as avg_amount
FROM orders
GROUP BY window| window_start | window_end | orders | avg_amount |
|---|---|---|---|
| 09:56:00 | 10:01:00 | 2 | 112.75 |
| 09:57:00 | 10:02:00 | 4 | 118.88 |
| 09:58:00 | 10:03:00 | 6 | 133.42 |
Moving Averages
Sliding windows are ideal for moving averages.
-- 5-minute moving average of metric values, updated every minute
SELECT
name,
HOP(interval '1 minute', interval '5 minutes') as window,
AVG(value) as moving_avg
FROM metrics
WHERE name = 'cpu_usage'
GROUP BY name, window| name | window_start | window_end | moving_avg |
|---|---|---|---|
| cpu_usage | 09:56:00 | 10:01:00 | 45.50 |
| cpu_usage | 09:57:00 | 10:02:00 | 48.83 |
| cpu_usage | 09:58:00 | 10:03:00 | 54.70 |
Session Windows
Dynamic windows based on activity gaps. A session ends after a period of inactivity.
SELECT
user_id,
SESSION(interval '30 minutes') as session,
MIN(timestamp) as session_start,
MAX(timestamp) as session_end,
COUNT(*) as page_views,
ARRAY_AGG(page ORDER BY timestamp) as pages_visited
FROM events
GROUP BY user_id, session| user_id | session_start | session_end | page_views | pages_visited |
|---|---|---|---|---|
| u1 | 10:00:05 | 10:00:45 | 4 | [/home, /products, /products/1, /checkout] |
| u2 | 10:00:18 | 10:01:15 | 3 | [/home, /about, /products] |
| u3 | 10:01:00 | 10:01:00 | 1 | [/home] |
Choosing the Right Window
| Use Case | Window Type | Example |
|---|---|---|
| Fixed reporting intervals | Tumbling | Hourly sales reports |
| Moving averages | Sliding | 5-min avg latency |
| User behavior analysis | Session | Engagement per visit |
| Real-time dashboards | Tumbling (small) | 10-second metrics |
| Trend detection | Sliding | Anomaly detection |
Window Fields
The window object contains useful fields.
SELECT
window.start as window_start,
window.end as window_end,
COUNT(*) as event_count,
SUM(amount) as total_amount
FROM orders
GROUP BY TUMBLE(interval '5 minutes') as window| window_start | window_end | event_count | total_amount |
|---|---|---|---|
| 10:00:00 | 10:05:00 | 8 | 1440.49 |
Combining Windows with Filters
Filter before windowing for efficiency.
SELECT
TUMBLE(interval '1 minute') as window,
COUNT(*) as error_count
FROM logs
WHERE level = 'ERROR'
GROUP BY window
HAVING COUNT(*) > 1| window_start | window_end | error_count |
|---|---|---|
| 10:01:00 | 10:02:00 | 2 |
The WHERE filters to errors first, the window aggregates, then HAVING filters to windows with more than 1 error.