Design an Ad Click Aggregation System
0%
Editorial Solution
System requirements
Functional
Capacity estimation
Click Volume
API design
Click Recording
Database design
Cassandra: Raw Click Event Storage
High-level design
Why Kappa Instead of Lambda
Request flows
Click Ingestion Flow
Detailed component design
Tumbling Windows and Watermarks
Trade offs/Tech choices
Lambda Architecture vs Kappa Architecture
Failure scenarios/bottlenecks
Aggregation Shard Crash
Future improvements
ML-Based Click Fraud Detection
Table of Contents
System requirements
Functional
Capacity estimation
Click Volume
API design
Click Recording
Database design
Cassandra: Raw Click Event Storage
High-level design
Why Kappa Instead of Lambda
Request flows
Click Ingestion Flow
Detailed component design
Tumbling Windows and Watermarks
Trade offs/Tech choices
Lambda Architecture vs Kappa Architecture
Failure scenarios/bottlenecks
Aggregation Shard Crash
Future improvements
ML-Based Click Fraud Detection
An ad click aggregation system looks like a counter on the surface: count how many times each ad gets clicked. The real challenge is that these counts directly drive billing. Advertisers pay per click, so every lost click is lost revenue and every double-counted click is overcharging. This is not a tally; it is a financial pipeline that must be accurate at massive scale, and the right mental model for the architecture is not "high-throughput web service" but "streaming aggregation with billing-grade correctness."
Two facts shape every decision in this design. First, clicks arrive late. A user on a mobile device clicks an ad in a tunnel, the click sits in a local buffer, and the network reconnects 30 seconds later. From the system's point of view the click arrives at minute N+1, but the click actually happened at minute N. If we naively bucket events by arrival time, the minute N count is wrong. Stream processing literature has a name for this problem: event time vs processing time, and the solution is watermarking. Any design that ignores it is incorrect at the core. Second, advertisers do not just want total counts; they want multidimensional breakdowns (clicks by country by device by hour by landing page) for ad-hoc analytics, which means the read path needs an OLAP store, not a key-value cache.
Functional
The capacity numbers for an ad click system are dominated by one insight: each click event is tiny (around 100 bytes), but the volume is enormous (1 billion per day) and the multidimensional breakdowns mean the aggregated state is also non-trivial. The system is write-heavy on ingestion but read-heavy on analytics queries, which is why the write path and read path use different storage engines.
Click Volume
1 billion clicks per day: Average throughput: 1B / 86,400 seconds = roughly 11,500 clicks per second. This is manageable for a well-provisioned system at the steady state.
The API surface is small. An ad click system has two core operations: record a click and query aggregated stats. Keeping the API minimal reduces the attack surface for click fraud, simplifies client SDK integration, and isolates the high-volume ingestion path from the lower-volume analytics path.
Click Recording
1POST /v1/clicks
2Headers:
3 X-Click-ID: <uuid generated by client SDK>
4 X-Ad-SDK-Version: <version string>
5Body: {
6 ad_id: string,
7 event_timestamp: ISO-8601, # when the click happened, set by client
8 client_ip: string,
9 user_agent: string,
10 cookies: string (optional),
11 referrer_url: string,
12 publisher_id: string,
13 ad_placement: string # "header", "sidebar", "interstitial", etc.
14}
15Response: 202 Accepted {
16 server_received_at: ISO-8601
17}The data model splits across three stores with fundamentally different access patterns. Cassandra holds the raw audit trail. An OLAP store (Druid or ClickHouse) holds the aggregated data for advertiser queries. Redis holds two specific things: the dedup set for click_ids and the top-N sorted set for instant ranking lookups. None of these stores is optional, and none can replace another, because each is in its sweet spot for one of the three workloads the system has to serve.
Cassandra: Raw Click Event Storage
1click_events
2 ad_id TEXT Partition Key (composite with day_bucket)
3 day_bucket INT Partition Key
4 event_ts TIMESTAMP Clustering Key (DESC)
5 click_id TEXT Clustering Key
6 server_ts TIMESTAMP
7 client_ip TEXT
8 user_agent TEXT
9 country TEXT
10 region TEXT
11 device TEXT
12 publisher_id TEXT
13 ad_placement TEXT
14 user_id TEXT (nullable, when known)
15 flags INT (bitfield: late_arrival, suspected_fraud, etc.)The architecture has three logical halves separated by Kafka topics. The ingestion half receives clicks, deduplicates, and writes them to durable storage and an event log. The aggregation half consumes the raw event log, performs event-time bucketing with watermarks, computes multidimensional counters, and emits aggregated results to a second event log. The serving half consumes the aggregated event log, populates the OLAP store and Redis top-N, and serves advertiser queries. This is the Kappa architecture: a single streaming pipeline serves both real-time and historical aggregation, with batch reprocessing implemented as Kafka replay rather than as a separate batch system.
Why Kappa Instead of Lambda
Three flows define the system: click ingestion (recording a click), advertiser query (reading aggregated stats), and reconciliation (verifying aggregated counts against raw events). Each flow shares the same in-process aggregation architecture but enters and exits the pipeline at different stages.
Click Ingestion Flow
The core engineering challenge (the GATE of this problem) is the Aggregation Service: an event-time aggregation pipeline that handles 100K clicks per second, performs multidimensional counter increments under tight memory bounds, manages in-memory state with snapshot-based recovery, supports late events via watermarks, and produces deterministic output that reconciliation can audit. Every design choice in this section is constrained by these requirements.
The GATE insight: streaming aggregation is not 'count things in a hash map.' It is 'maintain bounded in-memory state organized by event time, advance watermarks, flush windows on watermark expiry, snapshot state for recovery, and produce idempotent output to a downstream store.' Every word in that sentence is load-bearing. The naive design that 'just counts in a Redis hash' fails on event time, fails on watermarks, fails on bounded state, and fails on recovery. Understanding why each piece is necessary is the substance of the senior interview answer.
Tumbling Windows and Watermarks
Every architectural decision in this system involves a trade-off. Walking through them is how you demonstrate the design was deliberate rather than cargo-culted from a textbook.
Lambda Architecture vs Kappa Architecture
Lambda runs two pipelines: a streaming pipeline for real-time approximate counts and a batch pipeline (typically Hadoop or Spark) for historical exact counts. The streaming pipeline is fast but approximate; the batch pipeline is exact but slow. Advertisers see streaming counts in real time, then the batch results overwrite them when they catch up. Reconciliation between the two implementations is itself a hard problem, because subtle differences in event time handling, late events, dimension rounding, or rounding can produce small discrepancies that look like bugs.
The reliability of an ad click aggregation system is measured not by whether failures occur but by how the system behaves when they do. Each failure scenario maps to a specific architectural decision that mitigates it.
Aggregation Shard Crash
These extensions build on the existing architecture without breaking the core streaming pipeline. Each addresses a specific limitation of the current system.
Mid-level: Pick one extension (ML fraud detection or A/B testing) and explain how it plugs into the existing pipeline without changing the core architecture.
Senior: Walk through multi-region deployment with CRDT counters, explaining the local-write property and the global convergence guarantee. Address what happens during a partition between regions.
Staff: Design conversion attribution end to end, including the user identity resolution problem across devices and sessions, the storage model for the multi-day attribution window, and the choice of attribution model (last-touch vs multi-touch) and its implications for advertiser reporting.
ML-Based Click Fraud Detection
Try this problem yourself
Reading a solution is not the same as producing one under interview pressure. Design it on the whiteboard and get scored feedback.
Asked in these interview guides
Company guides that list this problem among their sample questions.
0%
Editorial Solution
System requirements
Functional
Capacity estimation
Click Volume
API design
Click Recording
Database design
Cassandra: Raw Click Event Storage
High-level design
Why Kappa Instead of Lambda
Request flows
Click Ingestion Flow
Detailed component design
Tumbling Windows and Watermarks
Trade offs/Tech choices
Lambda Architecture vs Kappa Architecture
Failure scenarios/bottlenecks
Aggregation Shard Crash
Future improvements
ML-Based Click Fraud Detection
Table of Contents
System requirements
Functional
Capacity estimation
Click Volume
API design
Click Recording
Database design
Cassandra: Raw Click Event Storage
High-level design
Why Kappa Instead of Lambda
Request flows
Click Ingestion Flow
Detailed component design
Tumbling Windows and Watermarks
Trade offs/Tech choices
Lambda Architecture vs Kappa Architecture
Failure scenarios/bottlenecks
Aggregation Shard Crash
Future improvements
ML-Based Click Fraud Detection