Design Ad Click Aggregation / Real-time Analytics
hardAd click aggregation ingests billions of click events per day, computes per-ad per-minute counts in near real time, emits to dashboards and billing. The same shape serves any streaming analytics — pageviews, IoT telemetry, fraud signals.
Key Concepts
ad_id — all events for one ad on one partition, preserving order and enabling stateful processing. Collectors stateless; Kafka backpressure protects downstream.(ad_id, salt) then merge), special routing for top ads. Lambda vs Kappa: Lambda (batch + stream + serving merge) is battle-tested but complex; Kappa (stream-only with replay) is simpler and increasingly the default.High-level design
Edge collector → Kafka (partitioned by ad_id, long retention).
Stream processor (Flink) → windowed aggregation → dedup → emit.
Sinks: OLAP (Druid/ClickHouse) for dashboards; data lake (S3) for replay; billing pipeline for invoicing.
Side: fraud detector reads raw stream; flags / removes bad clicks.
Dashboards / API → OLAP query.
Components
- Edge collector (stateless, batches to Kafka).
- Kafka (partitioned by ad_id; week+ retention for replay).
- Stream processor (Flink): keyed state per ad_id, tumbling windows.
- Dedup state (RocksDB-backed in Flink).
- OLAP store (Druid, ClickHouse, Pinot) for sub-second queries.
- Data lake (S3 + Parquet).
- Billing pipeline (separate; exactly-once or reconciled).
- Fraud detection service.
- Dashboards (Superset, Grafana, internal).
Stream processing details
Windowing: tumbling 1-minute (non-overlapping) for billing; sliding for trends.
Watermarks: declare 'I've seen all events up to time T'; bounded lateness.
Allowed lateness: late events trigger updates within window.
State backend: RocksDB-based for large state; in-memory for small.
Checkpointing: durable snapshots for recovery.
Exactly-once: Flink + Kafka transactions; idempotent sinks.
Architecture choice: Lambda vs Kappa
Lambda: batch (nightly Spark over raw events) + stream (Flink for fresh approximate); serving merges. Complex, two code paths, but battle-tested for correctness.
Kappa: stream-only; reprocess by replaying log when needed. Simpler. Depends on log retention.
Most modern designs are Kappa with Flink + Kafka long retention.
Some still use Lambda for billing because nightly Spark on raw events gives ironclad correctness.
Hot keys
One ad goes viral; 80% of traffic to one partition.
Mitigations:
- Producer pre-aggregation: SDK buffers and sends counts, not individual events.
- Salt keys at producer: split into (ad_id, salt) partitions; consumer merges.
- Special-case: route top ads to dedicated processors.
- Repartition mid-stream: hash by (ad_id, time bucket) instead of just ad_id.
Trade-offs
Exactly-once: achievable with Flink + transactional sinks, but slower and more complex.
At-least-once + dedup: simpler, requires idempotent sinks; standard in practice.
Druid vs ClickHouse: Druid for time-series rollups; ClickHouse for general OLAP.
Lambda: correctness via batch backup; double maintenance.
Kappa: simpler; depends on log retention and reprocessing performance.
Billing accuracy > analytics accuracy. Separate paths.