Design Ad-Click Aggregation (Lambda vs Kappa)
Count a billion clicks a day two ways at once — a seconds-fresh dashboard and a billing-grade total that reconciles to the penny. Idempotent dedup, watermarks, late-event restatement, and why the replayable log lets one Kappa path replace two.
System design · Systems. The source ↗
A free, interactive, animated visual explainer of Design Ad-Click Aggregation (Lambda vs Kappa) — built to be understood, not skimmed.
Questions
- What is an ad-click aggregation system?
- A pipeline that turns a firehose of raw click events into counts per ad per time bucket, serving two audiences at once: a real-time dashboard that must be seconds-fresh, and a billing system whose totals must be exact and reconciled. The hard parts are all consequences of that double mandate — deduplicating retried clicks, admitting events that arrive hours late, filtering fraud before an advertiser is charged, and doing it at a peak of ~1M clicks/s.
- How do you avoid billing an advertiser twice for a duplicate click?
- Make the aggregation idempotent. At-least-once ingest will deliver some clicks more than once, so every event carries a unique click_id and the aggregator dedups on it — a duplicate that lands in the same window is dropped, an upsert keyed by click_id applies once. Uber’s ad pipeline does exactly this: it "leverages Flink’s keyed state in the deduplication mapper function to keep track of previously seen events." Correctness comes from the processing being idempotent, never from the network promising exactly-once delivery.
- How are late ad-click events handled?
- With event-time windows, watermarks, and allowed lateness. Each click is bucketed by when it happened, not when it arrived; a watermark declares "event time has reached t" and lets the window emit a preliminary total. Late events within an allowed-lateness cap restate that bucket — a versioned, "preliminary → reconciled" number — while events beyond the cap are dropped or side-lined so billing has a hard cutoff. The dashboard shows the preliminary number seconds-fresh; billing waits for the reconciled one.
- Lambda vs Kappa for click aggregation — which and why?
- Lambda runs a batch layer and a speed layer in parallel and merges them; the batch layer eventually overrides the speed layer, but you maintain two codebases that must agree. Jay Kreps’ objection: "maintaining code that needs to produce the same result in two complex distributed systems is exactly as painful as it seems." Kappa keeps one stream-processing path and reprocesses by replaying the retained log. Given exactly-once streaming exists, Kappa is the better default here — the concession is that a big historical restatement means replaying a lot of log rather than reading a precomputed batch table.
- How do you handle a viral ad that overloads one partition (hot-key skew)?
- Partitioning by ad_id sends every click for a viral ad to one partition, melting it while others idle. The fix is salting plus combiners: split the hot key into ad_id#0..k so its clicks spread across k partitions, pre-aggregate each shard with a combiner, then sum the k partials into the true per-ad total. You trade a second merge step for parallelism on the one key that needed it.
Related explainers
- Design a Stream Processor with Exactly-Once
- Design a Distributed Cache
- Back-of-the-Envelope: the Numbers That Design Systems
- Threads, Locks & the Anatomy of a Race
- Consistency, Quorums & CAP
- How to Design a System in 60 Minutes
- Rate Limiting: Four Algorithms, Honestly Compared
- Idempotency & the Exactly-Once Illusion