<--- Back to all resources

Engineering

February 25, 2026

10 min read

Flink SQL Session Windows: Detecting User Activity Patterns

Learn how session windows in Flink SQL group events by periods of activity separated by gaps. Build user session analytics, timeout detection, and engagement tracking.

TL;DR: • Session windows group events that arrive within a configurable inactivity gap - they have no fixed size and close only when no new events arrive within the gap duration. • Use the SESSION() table-valued function to detect natural activity patterns like user sessions, machine bursts, and conversation threads. • Session windows are inherently per-key, making them ideal for user-level analytics, engagement scoring, and timeout detection.

Every meaningful interaction a user has with your application - browsing pages, clicking buttons, submitting forms - is a discrete event. Individually, these events tell you very little. But grouped into sessions based on natural activity patterns, they reveal how users actually engage with your product: how long they stay, what they do, and when they leave.

Session windows solve the fundamental problem of turning a flat stream of timestamped events into meaningful activity periods. Unlike tumbling or sliding windows that impose fixed, arbitrary time boundaries, session windows adapt to the data itself. They open when activity begins, grow as events arrive, and close only after a period of inactivity. This makes them uniquely suited for any analysis where the concept of “a session” is defined by continuous engagement rather than a clock.

Apache Flink SQL provides first-class support for session windows through the SESSION() table-valued function, making it straightforward to build session-based analytics directly in SQL without writing custom application code.

How Session Windows Work

A session window is defined by a single parameter: the inactivity gap. The mechanics are simple but powerful:

  1. Window creation: When an event arrives for a key (such as a user ID) and no active session window exists, a new window opens.
  2. Window extension: Each subsequent event for that key resets the inactivity timer. The window’s end boundary moves forward.
  3. Window closing: When no event arrives within the gap duration, the window closes and emits its result.

This means session windows have no fixed start time, no fixed end time, and no fixed duration. Two users visiting the same site at the same time will produce session windows of completely different lengths depending on their individual behavior.

Consider a user who views three pages at timestamps 10:00, 10:02, and 10:08, with a gap of 5 minutes. The first two events fall within a single session (2 minutes apart), but the third event arrives 6 minutes after the second, exceeding the gap. That produces two separate sessions: one covering 10:00-10:02 and another starting at 10:08.

Flink SQL exposes session windows through the SESSION() table-valued function, which follows the same pattern as TUMBLE() and HOP(). Here is the basic structure:

SELECT
  user_id,
  SESSION_START(event_time, INTERVAL '30' MINUTE)  AS session_start,
  SESSION_END(event_time, INTERVAL '30' MINUTE)    AS session_end,
  COUNT(*)                                          AS event_count
FROM TABLE(
  SESSION(
    TABLE page_views,
    DESCRIPTOR(event_time),
    INTERVAL '30' MINUTE
  )
)
GROUP BY user_id, window_start, window_end;

The three arguments to SESSION() are:

  • TABLE reference: The input table containing your event stream.
  • DESCRIPTOR: The time attribute column (must be a declared event-time or processing-time attribute).
  • INTERVAL: The inactivity gap duration that defines when a session closes.

The window_start and window_end columns are automatically added by the table-valued function and must be included in the GROUP BY clause.

Practical Examples

User Web Session Tracking

The most common use case is converting raw page view events into user sessions. This query calculates session duration and the number of pages viewed per session:

SELECT
  user_id,
  window_start                                       AS session_start,
  window_end                                         AS session_end,
  TIMESTAMPDIFF(SECOND, window_start, window_end)    AS duration_seconds,
  COUNT(*)                                           AS pages_viewed,
  MIN(page_url)                                      AS entry_page,
  MAX(page_url)                                      AS exit_page
FROM TABLE(
  SESSION(
    TABLE page_views,
    DESCRIPTOR(event_time),
    INTERVAL '30' MINUTE
  )
)
GROUP BY user_id, window_start, window_end;

Each row in the output represents one complete user session with its start time, end time, duration, and the pages that bookended the visit. A 30-minute gap mirrors the standard web analytics session definition.

IoT Device Burst Detection

For IoT workloads, session windows detect bursts of sensor activity separated by quiet periods. A manufacturing sensor might fire rapidly during a machine cycle and then go silent:

SELECT
  device_id,
  window_start                                       AS burst_start,
  window_end                                         AS burst_end,
  COUNT(*)                                           AS reading_count,
  AVG(temperature)                                   AS avg_temp,
  MAX(temperature)                                   AS peak_temp
FROM TABLE(
  SESSION(
    TABLE sensor_readings,
    DESCRIPTOR(event_time),
    INTERVAL '10' SECOND
  )
)
GROUP BY device_id, window_start, window_end;

A 10-second gap means that any pause longer than 10 seconds between readings marks the boundary between two separate machine cycles. This lets you analyze each burst independently - detecting anomalies within a cycle or tracking cycle frequency over time.

Customer Support Conversation Grouping

Support ticket events - messages, status changes, agent assignments - naturally cluster into conversations. Session windows group these without requiring explicit conversation IDs:

SELECT
  ticket_id,
  window_start                                       AS conversation_start,
  window_end                                         AS conversation_end,
  COUNT(DISTINCT agent_id)                           AS agents_involved,
  COUNT(*)                                           AS total_messages,
  TIMESTAMPDIFF(MINUTE, window_start, window_end)    AS resolution_minutes
FROM TABLE(
  SESSION(
    TABLE support_events,
    DESCRIPTOR(event_time),
    INTERVAL '15' MINUTE
  )
)
GROUP BY ticket_id, window_start, window_end;

A 15-minute gap captures the natural rhythm of support interactions, where a customer and agent may take several minutes between replies. If 15 minutes pass with no activity, the conversation is considered paused or resolved.

Session-Based Engagement Scoring

You can layer business logic on top of session windows to compute engagement scores. This example weights different event types to produce a per-session score:

SELECT
  user_id,
  window_start                                       AS session_start,
  TIMESTAMPDIFF(MINUTE, window_start, window_end)    AS duration_minutes,
  SUM(
    CASE event_type
      WHEN 'purchase'    THEN 10
      WHEN 'add_to_cart' THEN 5
      WHEN 'search'      THEN 3
      WHEN 'page_view'   THEN 1
      ELSE 0
    END
  )                                                  AS engagement_score,
  COUNT(DISTINCT event_type)                         AS action_diversity
FROM TABLE(
  SESSION(
    TABLE user_events,
    DESCRIPTOR(event_time),
    INTERVAL '30' MINUTE
  )
)
GROUP BY user_id, window_start, window_end;

Sessions with high engagement scores and diverse action types represent high-intent users, while sessions consisting of a single page view with a low score indicate bounce-like behavior.

Session Windows Are Per-Key

An important property of session windows is that they operate independently per key. When you GROUP BY user_id, each user gets their own session window lifecycle. User A might have a session lasting 45 minutes with 30 events, while User B has a 2-minute session with 3 events, even if both are active during the same wall-clock period.

This per-key behavior is what makes session windows so natural for user-level analytics. It also means the number of concurrent open windows scales with the number of active keys. If you have 100,000 active users, Flink is tracking 100,000 independent session timers.

The key column does not have to be a user ID. You can partition by device_id for IoT, ticket_id for support, account_id for billing events, or any column that defines the entity whose activity you want to sessionize.

Choosing the Right Gap Duration

The gap parameter is the single most important decision when configuring session windows. Too short, and you split legitimate sessions into fragments. Too long, and you merge distinct sessions into one.

Here are practical starting points by domain:

  • Web analytics: 30 minutes is the industry standard, matching Google Analytics. Users commonly pause to read content, switch tabs, or take short breaks.
  • Mobile apps: 5-15 minutes. Mobile usage tends to be more focused with shorter pauses.
  • IoT sensors: Seconds to low minutes, depending on reporting frequency. A sensor that reports every second might use a 10-second gap; one that reports every minute might use a 5-minute gap.
  • Customer support: 10-15 minutes. Agents and customers typically reply within this window during active conversations.
  • API activity: 5-10 minutes. Programmatic clients tend to work in rapid bursts with clear pauses between tasks.

The best way to calibrate your gap is to analyze the distribution of inter-event times in your data. Plot a histogram of the time between consecutive events for each key. You will typically see a bimodal distribution: a cluster of short gaps (within-session) and a cluster of long gaps (between-session). Set your gap duration in the valley between these two clusters.

State Management Considerations

Session windows present a unique state management challenge: they have no upper bound on duration. If events keep arriving within the gap, a session can stay open indefinitely. This has direct implications for Flink’s state size.

Every open session window requires Flink to maintain state: the partial aggregation results, the window boundaries, and the timer that triggers window closure. For high-cardinality keys with long sessions, this state can grow substantially.

Strategies for managing session window state:

  • Set realistic gap durations: A shorter gap closes windows sooner, releasing state earlier. Do not use a 24-hour gap when 30 minutes is appropriate.
  • Enforce maximum session duration: Flink SQL session windows do not have a built-in maximum duration parameter. If you need to cap session length, consider using MATCH_RECOGNIZE for more complex sessionization logic, or pre-process events to inject synthetic gap events after a maximum duration.
  • Configure state TTL: Flink’s state time-to-live (TTL) settings can clean up state for keys that become permanently inactive. This acts as a safety net against state leaks from orphaned sessions.
  • Monitor state size: Track Flink’s state backend metrics in production. Sudden growth in state size often indicates a misconfigured gap duration or an upstream data issue causing unexpectedly long sessions.

Event Time vs Processing Time

Session windows can operate on either event time (when the event actually occurred) or processing time (when Flink processes the event). The choice significantly affects accuracy.

Event time produces correct results even when events arrive late or out of order. The session window boundaries reflect the actual activity timeline. However, event-time processing requires watermarks to signal that all events up to a certain timestamp have arrived. Late events that arrive after the watermark has passed the gap threshold may be dropped or handled separately.

Processing time is simpler - no watermarks needed - but it produces session boundaries based on when events reach Flink, not when they actually happened. Network delays, backpressure, or reprocessing historical data can produce wildly inaccurate sessions.

For production analytics where correctness matters, event time is almost always the right choice. Processing time is acceptable for monitoring dashboards where approximate, low-latency results are sufficient.

When using event time, configure your watermark strategy to balance latency and completeness. An aggressive watermark (short allowed lateness) gives faster results but drops more late events. A conservative watermark (long allowed lateness) captures more events but delays window emission.

Session Windows vs Fixed Windows

Choosing between session windows and fixed windows (tumbling or sliding) depends on what question you are trying to answer.

Use session windows when:

  • You need to detect natural activity periods defined by user behavior, not arbitrary time boundaries.
  • Session duration and events-per-session are key metrics.
  • The concept of “a session” or “a burst” is central to your analysis.
  • Activity patterns vary significantly between keys.

Use tumbling windows when:

  • You need regular, predictable aggregation intervals (per-minute counts, hourly summaries).
  • All keys should be aggregated over the same time boundaries.
  • Downstream consumers expect results at fixed intervals.

Use sliding (hop) windows when:

  • You need overlapping time intervals for smoothed or rolling metrics.
  • You want to detect trends over recent periods (e.g., a 5-minute count updated every 1 minute).

In many real-world systems, you will use both. Session windows compute per-user session metrics, while tumbling windows produce time-series aggregates of those sessions (e.g., “sessions started per minute” or “average session duration per hour”).

Platforms like Streamkap make this layered approach practical by streaming database changes in real time via CDC into managed Flink, where you can write both session and fixed window queries against live data without provisioning infrastructure. The raw event stream from your production database becomes immediately available for sessionization and aggregation.

Session windows are one of Flink SQL’s most useful constructs precisely because they let the data define the boundaries. When your analysis depends on understanding natural patterns of activity - user engagement, device behavior, conversational flow - session windows give you the grouping semantics that fixed windows cannot.