◐ system-design/design-ad-system.md
45. Design an Ad Click / Counting System
Tests: extreme write throughput, deduplication, fraud detection, real-time aggregation, billing accuracy.
~4 min read·updated 5/29/2026
45. Design an Ad Click / Counting System
Tests: extreme write throughput, deduplication, fraud detection, real-time aggregation, billing accuracy.
45.1 Requirements
Functional
- Serve ads to users (auction in real-time).
- Track impressions, clicks, conversions.
- Bill advertisers based on outcomes.
- Detect click fraud.
- Real-time and batch reporting.
Non-functional
- Massive scale: 100B+ events/day.
- Sub-100ms ad serve latency.
- Strong correctness for billing (over-count = lost trust; under-count = lost revenue).
- Real-time reporting (~minutes lag).
- Fraud-resistant.
45.2 Estimates
- 100B events/day = ~1.2M events/sec average; peak 5M/sec.
- Per event: 200 bytes = ~250 GB/sec at peak.
- Storage per day after compression: 5-20 TB.
- Ad serve QPS: less (depends on traffic), but still 100K-1M+/sec.
45.3 Architecture (high level)
[ User browser ]──→[ Ad server ]──→ ad creative + tracking pixel
│
[ Tracking endpoint ]──→ Kafka (impressions, clicks)
│
[ Stream processor (Flink) ]
│
┌───────┼───────┬───────────────┐
↓ ↓ ↓ ↓
[ Real-time [ Fraud [ Hot [ Long-term
dashboards ] filter] counters ] warehouse (Druid/BQ) ]
[ Auction svc ] (real-time bidding)
[ Budget svc ]
[ Billing svc ]
45.4 Ad serving
When a user visits a publisher page with an ad slot:
- Browser → ad server with context (URL, user, device).
- Ad server runs an auction:
- Identify eligible campaigns (targeting, budget remaining).
- Each bidder bids (real-time bidding, RTB) or campaign has fixed price.
- Highest CPM wins.
- Ad creative + tracking URL returned.
- Browser renders ad → fires impression pixel.
Latency budget: < 100 ms total. Auction itself < 50 ms.
45.5 Click & impression tracking
Tracking pixel = tiny image (or beacon API call):
- Browser GET
/track?event=impression&ad_id=X&... - Server: validate signature → enqueue event → return 1×1 pixel.
Endpoint must be:
- Sub-10ms (fire and forget).
- Handle massive QPS (CDN edge ideally).
- Sign URLs to prevent forgery.
45.6 Stream processing
Kafka holds events. Flink (or Spark Streaming) consumes:
- Dedup by event_id (Bloom filter + state).
- Filter fraudulent clicks (rules + ML).
- Aggregate per (campaign, hour) → counters.
- Decrement budget.
- Update real-time dashboards.
45.7 Counters
Counters are the heart. Multiple stores:
Real-time (last hour)
Redis sorted sets, Druid, ClickHouse. Sub-second queries.
Aggregated (daily)
Materialized in OLAP store (BigQuery, ClickHouse).
Source (event log)
Kafka retention + S3 / GCS archive. Replayable forever.
Sharded counters
Per campaign, multiple counter shards. Hot campaigns (high QPS) get more shards. Read = sum across shards.
45.8 Click fraud detection
Click fraud is endemic. Sources:
- Bots clicking competitors' ads.
- Click farms.
- Self-click fraud by publishers.
- Ad stuffing (multiple ads in 1×1 px).
Detection layers:
- Rules: too many clicks from same IP, same user-agent, same time delta.
- Behavioral: mouse movement, dwell time, click coordinates.
- Network: known bad IPs (datacenter, proxy lists).
- ML: anomaly detection on click patterns.
- Post-hoc: re-check after clicks; reverse charges if fraud detected later.
45.9 Budget management
Each campaign has daily / total budget. Must stop serving when exhausted.
- Real-time budget tracker: sliding sum of spend.
- On every auction: check budget remaining; skip if exhausted.
- Stale budget (eventually consistent) → over-spend; mitigation: budget pacing (rate limit spend per hour).
45.10 Billing accuracy
Two questions:
- Did the click happen? (impression / click recorded)
- Was it valid? (not fraud)
Process:
- Real-time provisional billing (campaign metrics update).
- Daily reconciliation:
- Replay last 24h of events through fraud filter.
- Confirm valid clicks.
- Final invoice based on validated counts.
- Disputed clicks → manual review.
45.11 Reporting
Two tiers:
Real-time (minutes lag)
For advertisers monitoring active campaigns. Druid / Pinot / ClickHouse over recent events.
Batch / final
For invoicing and analytics. BigQuery / Snowflake on historical events.
45.12 Schema example
events (
event_id (UUID), -- for dedup
event_type (impression / click / conversion),
ts,
campaign_id, ad_id, creative_id,
publisher_id, user_id (hashed), device, geo,
url, referrer,
ip, user_agent (hashed for privacy)
)
Stored as Avro/Protobuf in Kafka; Parquet/ORC in warehouse.
45.13 Pricing models
- CPM (cost per mille): pay per 1000 impressions. Brand awareness.
- CPC (cost per click): pay per click. Performance ads.
- CPA / CPI (cost per action / install): pay only on conversion. Highest performance bar.
- CPV (cost per view): video ads.
Aggregations differ per model; counter pipeline supports all.
45.14 Privacy
- No raw PII in event log.
- Hash IP, user IDs.
- Honor Do Not Track / GPC.
- Consent management (TCF, IAB).
- Differential privacy in some aggregations.
Post-cookie world: contextual targeting, privacy sandbox APIs.
45.15 Failure modes
- Ad server down: publisher page shows fallback (house ad / blank).
- Tracking lost: under-count; reconcile via raw access logs.
- Counter divergence: reconcile from event log (source of truth).
- Fraud filter false positives: legit clicks discarded; advertiser support sees discrepancy.
45.16 Sketch for interview
[Browser]──→[Ad server]──→returns creative
│
[Auction svc] ←── eligible campaigns from [Campaign DB]
[Budget svc]
[Browser fires pixel]──→[Tracking endpoint]──→Kafka(events)
│
[Flink stream proc]
│
┌───────────────┼───────────────┐
↓ ↓ ↓
[Druid: real-time] [Fraud filter] [Counter store]
↓
[Budget update]
↓
[Reconciliation job]
↓
[Billing svc]
[BQ / Snowflake] for analytics
45.17 What an interviewer wants
- Pixel-tracking + Kafka for ingest at scale.
- Stream processor (Flink) for dedup, fraud, aggregation.
- Sharded counters for hot campaigns.
- Reconciliation: real-time provisional + batch final.
- Awareness of click fraud and ML detection.
45.18 Common pitfalls
- Synchronous DB writes per event → service melts.
- No dedup → over-charging.
- Single counter per campaign → hot key.
- Over-trust real-time counters for billing → revenue loss to fraud.
- No event log to replay → can't recover from bugs.
Key takeaways
- Event log + stream processing handles the volume.
- Sharded counters tolerate hot campaigns.
- Real-time provisional billing + batch final reconciliation balances UX and accuracy.
- Click fraud is endemic; layer rules + behavioral + ML.
- Privacy + consent are not optional in 2026.
// 2 views