Chapter 72: Real-Time Analytics

Real-time analytics systems process and analyze data as it arrives, enabling immediate insights and actions. Unlike batch systems that process data in scheduled intervals, real-time systems provide sub-second to second-level latency. This chapter covers the architectural patterns, technologies, and trade-offs for building analytics systems that handle millions of events per second.

Lambda Architecture

Lambda Architecture combines batch processing and stream processing to provide both accurate historical analysis and low-latency real-time results. Proposed by Nathan Marz (creator of Apache Storm) in 2011.

Lambda Architecture Components

graph TB
    Source[Data Sources
Events, Logs, Sensors] subgraph "Batch Layer" BatchStore[Immutable Master Dataset
HDFS, S3] BatchProcess[Batch Processing
Spark, MapReduce] BatchView[Batch Views
Pre-computed results] end subgraph "Speed Layer" Stream[Stream Processing
Flink, Storm, Spark Streaming] SpeedView[Real-Time Views
Incremental updates] end subgraph "Serving Layer" Merge[Merge Logic
Batch + Real-time] Query[Query Interface
REST API, GraphQL] end Source --> BatchStore Source --> Stream BatchStore --> BatchProcess BatchProcess --> BatchView Stream --> SpeedView BatchView --> Merge SpeedView --> Merge Merge --> Query User[User Queries] --> Query

Layer Details

1. Batch Layer (Accuracy)

  • Purpose: Compute accurate views from complete historical data
  • Storage: Immutable, append-only master dataset (HDFS, S3)
  • Processing: Apache Spark, Hadoop MapReduce
  • Frequency: Hourly or daily batch jobs
  • Output: Pre-computed batch views
  • Latency: Hours (fresh data delayed)

2. Speed Layer (Latency)

  • Purpose: Provide real-time views for recent data
  • Processing: Apache Flink, Storm, Spark Streaming
  • State: In-memory or fast storage (Redis, Cassandra)
  • Approximation: May sacrifice accuracy for speed
  • Output: Incremental real-time views
  • Latency: Seconds to subseconds

3. Serving Layer (Queries)

  • Purpose: Merge batch and real-time views for queries
  • Storage: Druid, Cassandra, HBase
  • Query logic: batch_view(old data) + speed_view(recent data)
  • Interface: REST API, SQL interface

Example: Website Analytics

Requirement: Show pageview counts by URL for any time range.

Batch Layer

  • Runs daily: aggregate all pageviews from past 90 days
  • Output: {"/home": 1M views, "/product": 500K views, ...}
  • Accurate but data is up to 24 hours old

Speed Layer

  • Processes pageview events in real-time
  • Maintains counts for last 24 hours
  • Output: {"/home": +5K views today, "/product": +2K views today}

Serving Layer

  • Query: "Show pageviews for /home in last 7 days"
  • Response: batch_view("/home", 6 days ago) + speed_view("/home", today)
  • Result: Accurate historical + up-to-the-second recent data

Lambda Architecture Pros & Cons

Pros Cons
Accurate results (batch layer recomputes from source) Complex: maintain two codebases (batch + stream)
Real-time results (speed layer compensates) Duplicate logic: same computation in both layers
Fault-tolerant: speed layer can be rebuilt from batch Higher operational cost: two systems to monitor
Scalable: batch and speed layers scale independently Data consistency issues during merge
Interview Tip: Explain Lambda as "best of both worlds" - batch for accuracy, stream for speed. Mention the key challenge: maintaining duplicate logic. When asked about trade-offs, discuss operational complexity vs accuracy guarantees. Good for systems that need both historical accuracy and real-time insights (e.g., fraud detection, business intelligence).

Kappa Architecture

Kappa Architecture simplifies Lambda by using only stream processing. Proposed by Jay Kreps (creator of Apache Kafka) as a response to Lambda's complexity. Everything is a stream, including historical data.

Kappa Architecture Diagram

graph TB
    Source[Data Sources
Events, Logs, Sensors] subgraph "Stream Storage" Kafka[Kafka
Durable Event Log
Retention: days to months] end subgraph "Stream Processing" Consumer1[Stream Processor 1
Current version] Consumer2[Stream Processor 2
Reprocessing old version] end subgraph "Serving Layer" ServingDB[Serving Database
Cassandra, Druid, ClickHouse] Query[Query Interface
REST API] end Source --> Kafka Kafka --> Consumer1 Kafka --> Consumer2 Consumer1 --> ServingDB Consumer2 --> |During reprocessing| ServingDB ServingDB --> Query User[User Queries] --> Query

Key Principles

1. Everything is a Stream

  • Historical data: replay from durable event log (Kafka)
  • Real-time data: process as it arrives
  • No distinction between batch and streaming

2. Reprocessing for Changes

  • Need to change logic? Start new stream processor
  • Replay all historical events from beginning
  • Write to new serving table
  • Cutover when reprocessing catches up

3. Single Codebase

  • One stream processing job handles all data
  • No separate batch/speed layer logic
  • Easier to maintain and test

Reprocessing Workflow

sequenceDiagram
    participant K as Kafka (30 days retention)
    participant Old as Old Processor (v1 logic)
    participant New as New Processor (v2 logic)
    participant DB1 as Serving DB v1
    participant DB2 as Serving DB v2

    Note over K: Historical events stored
    Old->>K: Processing real-time
    Old->>DB1: Write results

    Note over New: Deploy new version
    New->>K: Replay from beginning
(offset 0) New->>DB2: Write to new table Note over New: Reprocessing catches up Note over DB2: Validate results Note over DB1,DB2: Cutover traffic to v2 Old->>Old: Shutdown v1

Requirements for Kappa

  • Durable event log: Kafka with long retention (days to months)
  • Fast reprocessing: Must replay history in reasonable time
  • Stateless or fast state restore: Stream processor can rebuild state
  • Idempotent writes: Reprocessing same event produces same result
  • Storage for multiple versions: Serving layer holds old + new during cutover

Example: User Activity Counts

Scenario: Count user actions per day. Need to change counting logic.

Without Kappa (Painful)

  1. Update batch job code → wait 24 hours for next run
  2. Historical data reprocessed, but slow
  3. Real-time and batch out of sync during transition

With Kappa (Smooth)

  1. Deploy new stream processor (v2 logic)
  2. Replay Kafka from day 1 at high speed (parallel consumers)
  3. Write to new table: user_activity_v2
  4. When caught up, redirect queries to v2
  5. Shutdown old processor
Interview Tip: Kappa simplifies Lambda by eliminating the batch layer. Key enabler is Kafka's durable log with long retention. Mention the reprocessing workflow when logic changes. Discuss trade-offs: simpler architecture but requires fast reprocessing. Not suitable if historical data too large to replay (use Lambda instead).

Lambda vs Kappa Comparison

Aspect Lambda Architecture Kappa Architecture
Complexity High (two layers, duplicate logic) Low (single stream processing layer)
Code Maintenance Difficult (sync batch + stream code) Easy (one codebase)
Reprocessing Run batch job on all historical data Replay event log from Kafka
Latency Batch: hours, Speed: seconds Seconds (all data treated as stream)
Accuracy Batch: exact, Speed: approximate Exact (if exactly-once processing)
Storage HDFS/S3 (immutable dataset) Kafka (durable log with retention)
Data Volume Petabytes (batch handles large scale) Limited by Kafka retention
Best For Very large historical data, guaranteed accuracy Moderate data, need simplicity, fast reprocessing

When to Use Which

Use Lambda When

  • Historical data too large to replay (petabytes)
  • Batch processing tools already in place (Spark jobs)
  • Different algorithms needed for batch vs real-time
  • Regulatory requirements for exact accuracy

Use Kappa When

  • Historical data fits in Kafka retention (days to months)
  • Frequent logic changes (need fast reprocessing)
  • Team prefers simpler architecture
  • Stream processing can handle full data volume

Hybrid Approach

  • Use Kappa for recent data (last 30 days in Kafka)
  • Use batch for deep historical analysis (years in S3)
  • Combine results at query time
Interview Tip: Explain that Lambda predates modern stream processing capabilities. Kappa became viable with Kafka's durable log and exactly-once semantics in Flink/Kafka Streams. The choice depends on data volume and reprocessing needs. Many companies now prefer Kappa for simplicity unless data volume forces Lambda.

Materialized Views

Materialized views are pre-computed query results stored as tables. Instead of running expensive aggregations on-demand, the system pre-aggregates data and stores it for fast lookups.

Materialized View Concept

graph TB
    Raw["Raw Events Table
Billions of rows
user_id, action, timestamp"] Query1["Query: Daily Active Users
SELECT date, COUNT(DISTINCT user_id)
FROM events
GROUP BY date"] Query2["Query: Actions per User
SELECT user_id, COUNT(*)
FROM events
GROUP BY user_id"] MV1["Materialized View: daily_active_users
Pre-aggregated, fast"] MV2["Materialized View: user_action_counts
Pre-aggregated, fast"] Raw --> |Slow: scan billions| Query1 Raw --> |Slow: scan billions| Query2 Raw --> |Incremental update| MV1 Raw --> |Incremental update| MV2 MV1 --> |Fast: read aggregates| User[User Dashboard] MV2 --> |Fast: read aggregates| User

Refresh Strategies

Strategy How It Works Pros Cons
Full Refresh Recompute entire view from scratch Simple, guaranteed correct Expensive, high latency
Incremental Refresh Apply only new changes since last refresh Fast, low cost Complex logic, requires change tracking
On-Demand Refresh when view is queried (if stale) No wasted work Query latency unpredictable
Scheduled Refresh every N minutes/hours Predictable freshness May refresh unnecessarily
Streaming Continuously update as data arrives Always fresh, low latency Complex, requires stream processor

Incremental Materialized Views

Goal: Update materialized view without recomputing everything.

Example: COUNT

  • Old value: 1000 pageviews for /home
  • New events: 5 pageviews for /home
  • Incremental update: 1000 + 5 = 1005

Example: SUM

  • Old value: $10,000 total revenue
  • New events: $500 in new orders
  • Incremental update: $10,000 + $500 = $10,500

Challenge: AVG

  • Cannot incrementally update average directly
  • Solution: Store SUM and COUNT, compute AVG = SUM / COUNT
  • Incrementally update SUM and COUNT separately

Materialized Views in Practice

PostgreSQL Materialized Views

CREATE MATERIALIZED VIEW daily_sales AS
SELECT date, product_id, SUM(amount) AS total_sales
FROM orders
GROUP BY date, product_id;

-- Refresh manually
REFRESH MATERIALIZED VIEW daily_sales;

-- Refresh concurrently (non-blocking)
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales;

Apache Flink / Kafka Streams (Real-Time)

// Continuously update materialized view from stream
stream
  .groupBy(event -> event.productId)
  .aggregate(new SumAggregator())
  .to("materialized_view_topic");

Druid (Rollup)

  • Automatically rolls up data at ingestion time
  • Stores pre-aggregated metrics (SUM, COUNT, MIN, MAX)
  • Queries run on rolled-up data, not raw events
Interview Tip: Materialized views are a classic space-time trade-off: use more storage to get faster queries. Discuss incremental refresh as the key optimization. Mention that some aggregations (COUNT, SUM) are easy to update incrementally, while others (MEDIAN) require full recomputation. Real-time materialized views are essentially stream processing jobs that maintain aggregates.

Stream Processing for Analytics

Stream processing frameworks enable real-time analytics by computing aggregations, joins, and transformations on unbounded data streams.

Windowing in Stream Processing

Since streams are infinite, we use windows to group events into finite chunks for aggregation.

graph TB
    subgraph "Tumbling Window (Non-overlapping)"
        T1[Window 1
00:00 - 00:05] T2[Window 2
00:05 - 00:10] T3[Window 3
00:10 - 00:15] end subgraph "Sliding Window (Overlapping)" S1[Window 1
00:00 - 00:05] S2[Window 2
00:02 - 00:07] S3[Window 3
00:04 - 00:09] end subgraph "Session Window (Activity-based)" Ses1[Session 1
User active
then 30min gap] Ses2[Session 2
User active again] end

Window Types

Window Type Description Use Case
Tumbling Fixed-size, non-overlapping (e.g., every 5 min) Hourly sales totals, daily active users
Sliding Fixed-size, overlapping (e.g., 5 min window, slide every 1 min) Moving averages, rolling metrics
Session Variable-size, gap-based (e.g., close after 30 min inactivity) User sessions, clickstreams
Global Entire stream (all time) Total counts, all-time metrics

Event Time vs Processing Time

Event Time

  • Definition: When the event actually occurred (timestamp in event)
  • Pros: Accurate, handles late arrivals, replayable
  • Cons: Complex (watermarks, late data handling)
  • Example: User clicked button at 10:05:23 AM

Processing Time

  • Definition: When the event was processed by the system
  • Pros: Simple, no late data
  • Cons: Not reproducible, skewed by delays
  • Example: Event processed at 10:07:15 AM (2 min delay)

Handling Late Data

sequenceDiagram
    participant S as Stream
    participant W as Watermark
    participant Win as Window [10:00-10:05]
    participant Out as Output

    Note over S: Events arrive in order
    S->>Win: Event @ 10:01 ✓
    S->>Win: Event @ 10:03 ✓
    S->>Win: Event @ 10:04 ✓

    Note over W: Watermark = 10:05
(No more events before 10:05) W->>Win: Close window Win->>Out: Emit result: count=3 Note over S: Late event arrives! S->>Win: Event @ 10:02 (late) Win->>Out: Update result: count=4
(if allowed lateness) Note over Win: Allowed lateness expires Win->>Win: Discard late events

Apache Flink Example

// Real-time analytics: Count pageviews per URL in 5-minute windows
DataStream<PageView> stream = env.addSource(new KafkaSource(...));

stream
  .keyBy(view -> view.url)
  .window(TumblingEventTimeWindows.of(Time.minutes(5)))
  .allowedLateness(Time.minutes(1))  // Handle late data
  .aggregate(new CountAggregator())
  .addSink(new CassandraSink(...));  // Write to serving DB

Key Features

  • Event time: Use timestamp from event, not processing time
  • Watermarks: Track progress of event time
  • Allowed lateness: Accept events up to 1 min late, update results
  • Exactly-once: Flink guarantees no duplicates or lost data
Interview Tip: Explain windowing types with concrete examples. Emphasize event time vs processing time - event time is more complex but necessary for accurate analytics. Discuss the challenge of late-arriving data and how watermarks solve it. Mention exactly-once processing semantics (critical for financial data).

OLAP Cubes

OLAP (Online Analytical Processing) cubes pre-compute aggregations across multiple dimensions, enabling fast drill-down and slice-and-dice queries.

OLAP Cube Structure

graph TB
        Cube["Dimensions: - Product - Region - Time  Measures: - Revenue - Units Sold"]

        L0["Level 0: All combinations Product × Region × Time"]
        L1["Level 1: Two dimensions Product × Region Product × Time Region × Time"]
        L2["Level 2: One dimension Product Region Time"]
        L3["Level 3: Grand total All data"]

    Cube --> L0
    L0 --> L1
    L1 --> L2
    L2 --> L3

Cube Dimensions vs Measures

Concept Definition Examples
Dimensions Attributes to group by (categorical) Product, Region, Time, Customer Segment
Measures Numeric values to aggregate Revenue, Units Sold, Profit, Page Views
Hierarchies Drill-down levels within a dimension Time: Year → Quarter → Month → Day

OLAP Operations

1. Slice

Select a single value from one dimension.

  • Example: "Show sales for Region = 'North America'"
  • Result: Reduces 3D cube to 2D (Product × Time)

2. Dice

Select a sub-cube by filtering multiple dimensions.

  • Example: "Show sales for Product IN ('A', 'B') AND Time IN (Q3, Q4)"
  • Result: Smaller 3D cube

3. Drill-Down

Navigate from summary to detail.

  • Example: Year → Quarter → Month → Day
  • Use case: User clicks "Q4" to see monthly breakdown

4. Roll-Up

Aggregate from detail to summary.

  • Example: Day → Month → Quarter → Year
  • Use case: Summarize daily sales into monthly totals

5. Pivot (Rotate)

Swap dimensions to change perspective.

  • Example: Change from "Products by Region" to "Regions by Product"
  • Use case: Excel pivot tables

Storage: MOLAP vs ROLAP vs HOLAP

Type Full Name Storage Pros Cons
MOLAP Multidimensional OLAP Pre-computed cube in optimized format Fastest queries (sub-second) Slow to build, storage explosion
ROLAP Relational OLAP Query relational DB on-the-fly No pre-computation, flexible Slower queries (seconds)
HOLAP Hybrid OLAP Hot data in MOLAP, cold in ROLAP Balanced speed and cost Complex to manage

Cube Explosion Problem

Problem: Pre-computing all combinations explodes exponentially.

Example

  • 10 dimensions, each with 100 values
  • Possible combinations: 100^10 = 10^20 cells
  • Even with sparse data, storage becomes massive

Solutions

  • Partial cubes: Pre-compute only common queries
  • Aggregation hierarchies: Store only summary levels
  • Sparse storage: Don't store empty cells
  • Sampling: Approximate results for large cubes
Interview Tip: OLAP cubes are a space-time trade-off taken to the extreme. Explain the cube explosion problem and mitigation strategies. Mention real-world tools: Apache Druid (MOLAP-like), ClickHouse (ROLAP-like). Discuss hierarchies (time: year → month → day) for drill-down. Good for BI dashboards with fixed query patterns.

Real-Time Dashboards

Real-time dashboards display up-to-the-second data, requiring efficient data push mechanisms and optimized queries.

Dashboard Architecture

graph TB
    subgraph "Data Sources"
        Stream[Kafka Streams]
        DB[Analytical DB
Druid, ClickHouse] end subgraph "Backend" API[API Server
Node.js, Go] Cache[Redis Cache
Hot metrics] WS[WebSocket Server
Real-time push] end subgraph "Frontend" Browser[Browser
React, Vue] Chart[Chart Library
D3.js, Grafana] end Stream --> |Aggregations| Cache DB --> API Cache --> API API --> WS WS --> |Push updates| Browser Browser --> Chart Browser --> |Poll fallback| API

Update Mechanisms

Mechanism How It Works Pros Cons
WebSocket Push Server pushes updates to clients Instant updates, efficient Complex, connection management
Server-Sent Events (SSE) Server sends events over HTTP Simple, HTTP-based One-way only (server → client)
Short Polling Client requests updates every N seconds Simple, works everywhere High latency (up to N seconds)
Long Polling Client waits for update, server responds when available Lower latency than short polling Inefficient, connection overhead

Optimization Techniques

1. Caching Hot Metrics

  • Store frequently queried metrics in Redis
  • Update cache from stream processor
  • Serve dashboard queries from cache (sub-ms latency)
  • Example: "Current active users" updated every second

2. Pre-Aggregation

  • Stream processor computes aggregates (hourly, daily)
  • Dashboard queries pre-aggregated data, not raw events
  • Reduces query time from seconds to milliseconds

3. Downsampling

  • Reduce resolution for old data
  • Example: Last hour: 1-second granularity → Last day: 1-minute → Last month: 1-hour
  • Saves storage and speeds up queries

4. Query Result Caching

  • Cache dashboard query results for 5-30 seconds
  • Many users viewing same dashboard hit cache
  • Trade-off: slight staleness for massive speedup

Grafana-Style Dashboard

Architecture

  • Data source: Prometheus, InfluxDB, Druid
  • Queries: PromQL, SQL, custom DSL
  • Refresh interval: 5s to 1min (configurable)
  • Alerting: Trigger alerts on threshold breach

Best Practices

  • Limit panels per dashboard (5-10) to avoid query overload
  • Use variables for filtering (time range, region, service)
  • Pre-aggregate data for historical views (rollups)
  • Optimize queries: avoid SELECT * , use indexed columns

Handling Dashboard Load

graph LR
    Users[1000 Users
Viewing Dashboard] LB[Load Balancer] subgraph "API Tier (Stateless)" API1[API 1] API2[API 2] API3[API 3] end subgraph "Caching Layer" Redis[Redis
Query result cache
TTL: 10s] end subgraph "Database" DB[ClickHouse
Analytical DB] end Users --> LB LB --> API1 LB --> API2 LB --> API3 API1 --> Redis API2 --> Redis API3 --> Redis Redis --> |Cache miss| DB DB --> Redis

Real-World Example: Uber's Real-Time Dashboard

Requirements

  • Show active rides, driver availability, demand heatmaps
  • Update every 1-5 seconds
  • Support 10,000+ simultaneous viewers (ops team, execs)

Solution

  • Stream processing: Flink aggregates ride events by city, time window
  • Serving layer: Pre-aggregated data in Pinot (real-time OLAP)
  • Caching: Redis caches common queries (city-level metrics)
  • Push updates: WebSocket pushes new data to connected clients
  • Result: Sub-second latency for 10K+ concurrent users
Interview Tip: Discuss the trade-off between freshness and load. WebSockets enable instant updates but require connection management. Polling is simpler but increases server load. Mention caching as the key to scaling - cache query results for 10-30 seconds to handle thousands of users viewing the same dashboard. Explain downsampling for historical data.

Key Takeaways

  • Lambda Architecture combines batch (accurate) and speed (low-latency) layers, at the cost of duplicate logic and complexity.
  • Kappa Architecture simplifies Lambda by treating everything as a stream, enabled by Kafka's durable log and fast reprocessing.
  • Materialized views pre-compute aggregations for fast queries. Incremental refresh is key to efficiency.
  • Stream processing enables real-time analytics with windowing (tumbling, sliding, session) and event time semantics.
  • OLAP cubes pre-compute multi-dimensional aggregations, enabling fast drill-down but risking storage explosion.
  • Real-time dashboards require caching, pre-aggregation, and efficient push mechanisms (WebSocket, SSE) to scale.