Markdown Editor

Ad Click Aggregation System Design Template (Reference Design)

45-minute path, click dedupe, event-time windows, exactly-once counting, exact and Count-Min top-K, batch reconciliation for billing, failure modes, 10x row

System DesignFullCount-Min sketch (Cormode and Muthukrishnan 2005); Apache Kafka delivery semanticsdesignclickaggregationmath

How to use: Use it when designing click, impression or usage counting where some numbers feed invoices. Fill the assumptions and the billing source-of-truth decision first, then the exactly-once and reconciliation sections. The main pitfall is billing from streaming counts that late events and replays can still change.

Preview

System Design: [Ad click aggregation system name]

Reference design for an ad click aggregation pipeline that turns a stream of raw click events into per-ad counts by minute and hour, top-K rankings and billing-grade daily totals. It covers click deduplication, event-time windows and late events, exactly-once counting with Kafka transactions and idempotent sinks, exact and approximate top-K (the Count-Min sketch of Cormode and Muthukrishnan, Journal of Algorithms 55(1), 2005, and the Space-Saving algorithm), and the batch reconciliation that makes streaming numbers safe to bill. It follows the structure of the System Design Document template, adds a 45-minute interview path, access-pattern and failure-mode tables and a cited "How real systems do it" box, and is pre-filled with a worked design to adapt. Replace every [bracketed] value and every row marked Example; values marked Example are assumptions, not benchmarks.

Document control

FieldValue
Document ID[SDD-ADC-001]
System / feature[Ad click aggregation and reporting]
Version[0.1]
Status[Draft / In review / Approved / Implemented / Superseded]
Owner[@owner]
Author(s)[@author]
Reviewers[@reviewer-1], [@reviewer-2]
Last updatedYYYY-MM-DD
Classification[Internal]
DifficultyAdvanced
Billing impact[Yes: daily totals feed advertiser invoices]
Related[Stream processing engine design], [Invalid traffic filtering spec], [ADR: billing source of truth]

Approvals

RoleNameDecisionDate
Tech lead[@name][Approved / Changes requested]YYYY-MM-DD
Staff / principal engineer[@name][Decision]YYYY-MM-DD
Data platform / SRE[@name][Decision]YYYY-MM-DD
Finance / billing owner[@name][Decision]YYYY-MM-DD
Security and privacy[@name][Decision]YYYY-MM-DD

Revision history

VersionDateAuthorChange summary
0.1YYYY-MM-DD[@name]First draft for design review
0.2YYYY-MM-DD[@name]Example: Moved billing to reconciled batch totals after reviewer comments on late events
0.3YYYY-MM-DD[@name]Example: Added Count-Min sketch top-K for the publisher dimension
1.0YYYY-MM-DD[@name]Example: Approved

Contents

1. Overview

1.1 Problem statement

[Example: Clicks are counted today by a nightly batch job over web server logs. Advertisers see yesterday's numbers only, budget pacing reacts a day late, and two incidents last quarter double-counted clicks when a log shipper retried a file. The sales team wants live dashboards and a "top ads right now" view, and finance wants one number per ad per day that it can defend in a billing dispute.]

1.2 Proposed solution

[Example: A click service records every click as an event with a unique, signed click ID, redirects the user to the advertiser, and writes the event to a Kafka topic partitioned by ad ID. A stream processor deduplicates click IDs, filters invalid traffic flagged upstream, counts clicks per ad in one-minute event-time windows and writes the counts with Kafka transactions and idempotent upserts into an analytics store. Top-K rankings are exact for the ad dimension and approximate (Count-Min sketch with a heap) for the high-cardinality publisher dimension. Raw events are also archived to object storage, and an hourly and daily batch job recomputes counts from the archive and reconciles them with the streaming numbers. Invoices use only the reconciled daily totals.]

1.3 Decisions requested from reviewers

IDQuestionRecommended optionSection
Q-01Example: Which numbers can be billed?Only reconciled daily totals from the batch path; streaming numbers are labelled provisional7.5
Q-02Example: How do we stop duplicates?Signed click ID, keyed dedupe state for 1 h, transactional sink7.2, 7.3
Q-03Example: How late may a click arrive?10 min allowed lateness in the stream; later clicks are corrected by batch7.4
Q-04Example: Exact or approximate top-K?Exact for ads; Count-Min sketch plus heap for ad by publisher by country7.6
[Q-nn][Question][Option][Section]

1.4 45-minute interview path

Use this order when the document serves an interview or a short design review. The written sections below are the long form of each step.

MinutesCoverOutput on the whiteboard
0 to 5Scope: count clicks per ad per minute, top-K, billing totals; below-the-line items (2.3)Three queries the system must answer
5 to 10Estimates: clicks per second, distinct keys per window, dedupe state (section 3)Four numbers, each with its consequence
10 to 15Interface: click redirect, metrics query, top-K query (section 4)Three calls
15 to 25High-level design: click service, log, stream aggregator, OLAP store, archive and batch (section 6)One diagram with a hot and a cold path
25 to 40One deep dive: exactly-once counting (default, 7.2 and 7.3), or late events and windows (7.4), or top-K at scale (7.6)Options, choice, reason
40 to 45Reconciliation, failures (aggregator crash, duplicate replay), the 10x row in section 10Two failure rows

Start the default deep dive with the naive answer, a counter per ad incremented in a database on each click, and show the two ways it fails: the row for a popular ad becomes a write hotspot, and a retried request counts twice. Then move to a partitioned log, keyed state and a transactional sink, and say exactly which step removes which duplicate.

2. Requirements

2.1 Core functional requirements

IDRequirementPriority
FR-01Example: Record each click and redirect the user to the landing pageMust
FR-02Example: Count clicks per ad per one-minute event-time window, with country and device breakdownMust
FR-03Example: Roll minute counts up to hourly and daily countsMust
FR-04Example: Return the top 100 ads by clicks for the last 1, 5 and 60 minutes, globally and per countryMust
FR-05Example: Produce reconciled daily totals per ad for billingMust
FR-06Example: Exclude clicks flagged as invalid traffic, and keep them countable for auditsMust
FR-07Example: Return top publishers per ad (high-cardinality, approximate allowed)Should
FR-08Example: Recompute any past day from raw events on requestShould
[FR-nn][Requirement][Must / Should / Could]

2.2 Non-functional requirements

IDQualityTargetMeasured as
NFR-01FreshnessExample: Minute counts queryable within 60 s of window close (p95)Watermark lag plus sink lag
NFR-02Correctness (billing)Example: Reconciled daily total equals the deduplicated raw count; zero toleranceBatch recount vs. invoice input
NFR-03Correctness (dashboard)Example: Streaming minute counts within 0.1% of the reconciled count per ad per hourHourly reconciliation report
NFR-04Click latencyExample: Redirect p99 at or below 50 ms; logging never blocks the redirect beyond 5 msClick service timing
NFR-05DurabilityExample: No accepted click lost; raw events retained 400 daysIngest vs. archive counts
NFR-06Query latencyExample: Dashboard query for one ad, 7 days by hour, p95 under 500 msOLAP query logs
[NFR-nn][Quality][Target][Measurement]

2.3 Below the line (out of scope)

Name these explicitly so reviewers and interviewers do not pull the design toward them.

  • [Example: invalid traffic and click-fraud scoring models (a separate service sets a flag on the event; see the fraud detection design)]
  • [Example: impression counting and click-through rate (same pattern, separate pipeline)]
  • [Example: conversion attribution and multi-touch models]
  • [Example: the ad auction and budget pacing logic that consume the counts]
  • [Item deferred, with the release that will take it]

3. Capacity estimation

3.1 Assumptions

InputSymbolValueSource
Clicks per dayCCExample: 1.2 billionClick logs x growth
Peak to average ratioffExample: 5Hourly traffic curve
Bytes per click event (serialised)eeExample: 250 BEvent schema in 5.1
Distinct (ad, country, device) keys per minuteKmK_mExample: 1 millionSampled logs
Distinct (ad, country, device) keys per hourKhK_hExample: 4 millionSampled logs
Bytes per aggregate row incl. overheadrrExample: 60 BOLAP store estimate
Dedupe window for click IDsTdT_dExample: 1 hRetry and replay behaviour
Bytes per dedupe entry in keyed statessExample: 60 B16-byte ID plus state overhead
Events per second one aggregation task sustainsccExample: 10 000Benchmark

3.2 Event rate and bandwidth

λˉ=C86 400=1.2×10986 400≈13 889 clicks/s,λpeak=5×13 889≈69 444 clicks/s\bar{\lambda} = \frac{C}{86\,400} = \frac{1.2 \times 10^{9}}{86\,400} \approx 13\,889 \ \text{clicks/s}, \qquad \lambda_{peak} = 5 \times 13\,889 \approx 69\,444 \ \text{clicks/s}

At 250 B per event that is 300 GB per day and about 17.4 MB/s at peak. Kafka retention of 7 days with replication factor 3 holds 300×7×3=6.3300 \times 7 \times 3 = 6.3 TB. Consequence: bandwidth and log storage are small, so the design is shaped by state and correctness, not by throughput.

3.3 Aggregation tasks and partitions

Ntasks=λpeakc=69 44410 000≈6.9N_{tasks} = \frac{\lambda_{peak}}{c} = \frac{69\,444}{10\,000} \approx 6.9

Run 32 tasks (more than 4x headroom for replays, which must catch up faster than real time) over 128 Kafka partitions. Consequence: create 128 partitions from day one, because changing the partition count remaps ad IDs to partitions and breaks per-key ordering; 128 still covers the 10x case (about 70 tasks) in section 10.

3.4 Dedupe state

Sdedupe=λpeak×Td×s=69 444×3 600×60 B≈15 GBS_{dedupe} = \lambda_{peak} \times T_d \times s = 69\,444 \times 3\,600 \times 60 \ \text{B} \approx 15 \ \text{GB}

That is 250 million click IDs if the whole hour ran at peak, an upper bound. Spread over 32 tasks it is about 0.47 GB per task. Consequence: the dedupe state fits in an embedded on-disk state backend (RocksDB) per task with incremental checkpoints; no external lookup per click is needed.

3.5 Aggregate storage

TableRows per daySize per dayRetentionTotal
Minute countsKm×1 440K_m \times 1\,440 = 1.44 billion86.4 GB30 days2.59 TB
Hourly countsKh×24K_h \times 24 = 96 million5.76 GB730 days4.2 TB
Raw archive (compressed 5x)1.2 billion events60 GB400 days24 TB

Consequence: minute rows are the big table; keep them 30 days in a columnar OLAP store partitioned by day and roll older data up to hours.

3.6 Top-K state

Exact counts for the ad dimension need one counter per key per window: about 1 million keys x 64 B = 64 MB per minute window across the cluster, which is cheap. The publisher dimension (ad x publisher x country) has about 30 million keys per minute, or 1.92 GB per minute window, multiplied by every open window. A Count-Min sketch with ε=10−4\varepsilon = 10^{-4} and δ=10−3\delta = 10^{-3} needs w=⌈e/ε⌉=27 183w = \lceil e/\varepsilon \rceil = 27\,183 columns and d=⌈ln⁡(1/δ)⌉=7d = \lceil \ln(1/\delta) \rceil = 7 rows, 190 281 four-byte counters or about 761 KB per task and window. Consequence: exact top-K for ads, sketch-based top-K for the publisher dimension (7.6).

3.7 Summary

QuantityEstimateDesign consequence
Peak clicksAbout 69 000/s128 partitions, 32 tasks
Raw log300 GB/day, 6.3 TB in KafkaThroughput is not the hard part
Dedupe stateUp to 15 GB, about 0.47 GB per taskLocal keyed state, no remote lookup
Minute rows1.44 billion/dayColumnar store, 30-day retention, roll-ups
Sketch per task and windowAbout 761 KBApproximate top-K for the high-cardinality dimension

4. API design

4.1 Interfaces

InterfaceOperationPurpose
Click endpointGET /c/{click_token}Validate the signed token, log the click, return 302 to the landing page
Metrics APIGET /v1/ads/{ad_id}/clicks?from=&to=&granularity=minute|hour|day&group_by=countryTime series for dashboards
Top-K APIGET /v1/top-ads?window=1m|5m|60m&k=100&country=Ranking for the latest closed window
Billing exportGET /v1/billing/daily?date=&advertiser_id=Reconciled daily totals with a reconciliation version
AdminPOST /v1/recomputeRecompute a day from the raw archive

4.2 Click token and event

The click token is minted when the ad is served and signed with a server key (HMAC), so a click ID cannot be forged and the same click always carries the same ID. Redirects are logged before the 302 is returned, but the write is an asynchronous append to a local buffer that is flushed to Kafka; the redirect never waits for a Kafka acknowledgement.

{"click_id": "c_01JD4Q8V6R2M7X9K3T5W0YH1ZB", "ad_id": "ad_48213", "campaign_id": "cmp_902", "advertiser_id": "adv_77", "publisher_id": "pub_5521", "event_time": "2026-10-30T09:14:07.412Z", "country": "DE", "device": "mobile", "ivt_flag": false, "schema_version": 3}

4.3 Top-K response

Every response says which window it describes and whether the numbers are provisional, so a client never mistakes a streaming count for a billable one.

{"window_start": "2026-10-30T09:14:00Z", "window_end": "2026-10-30T09:15:00Z", "k": 3, "status": "provisional", "watermark": "2026-10-30T09:15:31Z", "items": [{"ad_id": "ad_48213", "clicks": 18342}, {"ad_id": "ad_10077", "clicks": 15120}, {"ad_id": "ad_33190", "clicks": 9981}]}

5. Data model

5.1 Entities

Raw click events are immutable; every count is derived from them and can be rebuilt from the archive.

5.2 Storage choices

DataStoreKeyMain queriesWhy
Raw clicks (hot)Example: Kafka topic, 128 partitions, 7 daysPartition by ad_idStream consumption, replayOrdered per ad, replayable
Raw clicks (cold)Object storage, columnar files by hourdt, hourBatch recount, auditsCheap, immutable, 400 days
Dedupe and window stateEmbedded state backend per task, checkpointed to object storageclick_id, (ad_id, window)Point lookups per eventLocal, no network hop
Minute and hourly countsColumnar OLAP store(ad_id, window_start), partitioned by dayTime series per ad, group by countryFast scans and roll-ups
Billing totalsRelational database(ad_id, day, recon_version)Invoice generationTransactions, audit trail
Top-K snapshotsKey-value cache(window_id, scope)Latest rankingOne read per dashboard refresh

5.3 Access patterns

Access patternFrequencyKey or index that serves itPartition key rationale
Deduplicate one clickEvery click (about 69 000/s at peak)Keyed state on click_id in the task that owns the adThe click ID is checked in the task that owns the ad, so the same click always lands in the same task
Add a click to its windowEvery clickKeyed state on (ad_id, window_start, country, device)Partitioning by ad_id keeps all clicks of one ad in one task, so counts need no cross-task merge
Dashboard time series for one adAbout 2 000 queries/sOLAP primary key (ad_id, window_start)Sort by ad then time makes a 7-day query one contiguous range
Latest top-KEvery dashboard refreshCache key (window_id, scope)Precomputed per window, no aggregation at read time
Daily billing totalsOnce per day per advertiserRelational index on (advertiser_id, day)Small table, needs transactions and history
Recount one hour from rawHourly batch, ad hocObject storage path dt=/hour=Hour partitions let the batch read only what it reconciles

6. High-level design

There are two paths: a hot streaming path for fresh, provisional numbers and a cold batch path that produces the reconciled, billable numbers.

ComponentResponsibilityScales by
Click serviceValidate token, redirect, append event to local bufferStateless instances behind the load balancer
Local event bufferBatch events and produce to Kafka with idempotence onWith click service
Kafka clicks topicOrdered, replayable log partitioned by adPartitions and brokers
Stream aggregatorDedupe, filter invalid traffic, window and count, write transactionallyTasks up to the partition count
Top-K mergerMerge per-task top-K lists and sketches per windowOne small job per window size
ArchiverWrite hourly columnar files to object storageConsumer group
Batch recount and reconcilerRecount from raw, compare, publish billing totals and correctionsBatch cluster size
Query APIServe dashboards, top-K and billing exportsStateless instances

7. Detailed design

The deep dives in this section, each with options, choice and reason:

Deep diveOptions consideredChoiceReasonSection
Duplicate removalTrust the source; database unique key; keyed dedupe state; Bloom filterSigned click ID plus keyed dedupe state for 1 hExact, local, and a Bloom false positive would drop a real, billable click7.2
Exactly-once countingAt-least-once plus idempotent sink; Kafka transactions; two-phase commit to the storeCheckpointed state plus transactional or idempotent upsert sinkReplays after a crash do not change any count7.3
Late eventsProcessing-time windows; event-time windows with watermark; batch onlyEvent-time windows, 10 min allowed lateness, batch corrects the restCounts belong to the minute the click happened7.4
Billing source of truthStreaming counts; batch counts; both reconciledReconciled batch totalsOne defensible number per ad per day7.5
Top-KExact counts everywhere; Count-Min sketch plus heap; Space-SavingExact for ads, sketch plus heap for publishersState bounded where cardinality is high7.6

7.1 Click flow

If the token signature fails, the click service still redirects (users must never see an error for an ad click) but writes the event with an invalid_token reason to a separate topic for the fraud team.

7.2 Duplicate removal

Duplicates enter at three places, and each has its own control:

Source of duplicateExampleControl
User or browserDouble click, page reload, prefetchSame signed click_id; dedupe state drops the second copy
Producer retryBuffer resends a batch after a timeoutKafka idempotent producer drops the resent records within a partition
Consumer replayAggregator restarts from the last checkpointState and offsets restored together (7.3); output written transactionally

Dedupe state keeps each click ID for one hour after its event time, with a TTL so state stays near the 15 GB bound in 3.4. A Bloom filter would use less memory, but a false positive drops a genuine click without trace, which is a billing error; exact keyed state is affordable at this scale, so it wins.

7.3 Exactly-once counting

"Exactly once" here means each accepted click changes the published counts exactly once, even when tasks crash and replay. Three mechanisms combine:

  1. Source: the aggregator reads Kafka with isolation.level=read_committed and keeps its offsets in its own checkpoints, not in auto-commit.
  2. State: periodic checkpoints (every 30 s) capture offsets, dedupe state and open windows together. After a crash the task restores the last checkpoint and re-reads from the saved offsets, so state and input agree.
  3. Sink: window results are written either as a Kafka transaction that commits with the checkpoint (when the next stage is another topic), or as an idempotent upsert keyed by (ad_id, window_start, country, device) that overwrites rather than increments (when the sink is the OLAP store). A replayed window writes the same row with the same value.

The rule to review: never write increments (clicks = clicks + n) to an external store from a stream job; write absolute window values that a replay can repeat safely.

7.4 Windows, watermarks and late events

Clicks are counted by event_time, not arrival time, in tumbling one-minute windows. The watermark trails the largest event time seen by 30 s, so a window emits a provisional result about 30 s after it ends. Windows stay open for 10 minutes of allowed lateness; a late click updates the window and the sink overwrites the row with the new absolute value. A click later than 10 minutes goes to a side output that is counted only by the batch path (7.5). Report the late share as a metric; if it rises, mobile clients are probably buffering offline.

7.5 Reconciliation and the billing source of truth

The batch job runs every hour for the previous hour and once a day for the whole day, after a 2-hour delay so stragglers land in the archive. It deduplicates raw events by click_id, drops invalid traffic and counts per ad. The reconciler then compares batch and streaming counts per ad and hour:

OutcomeRuleAction
MatchDifference at or below 0.1% (NFR-03)Mark the hour reconciled
DriftDifference above 0.1% for any adOverwrite streaming rows with batch values, open an incident
Missing dataBatch count far below stream countArchiver gap: block billing for the day, page on-call

Daily billing totals always come from the batch count, stored with a reconciliation version. A correction after invoicing creates a new version and an adjustment line, never an edit in place.

7.6 Top-K

Because Kafka is partitioned by ad_id, every ad's full count lives in one task. Each task keeps exact counts per window and emits its local top 100; the merger takes the top 100 of the union, which is exact because no ad is split across tasks. Five-minute and 60-minute rankings are computed from the stored minute counts with sliding sums.

The publisher dimension (ad x publisher x country) is too wide for exact counts per window (3.6). Each task keeps a Count-Min sketch of dd rows and ww columns plus a min-heap of the current heaviest keys. For a key xx the estimate is the minimum over rows:

a^x=min⁡j=1d count[j,hj(x)],ax≤a^x≤ax+εN with probability at least 1−δ\hat{a}_x = \min_{j=1}^{d} \ count\left[j, h_j(x)\right], \qquad a_x \le \hat{a}_x \le a_x + \varepsilon N \ \text{with probability at least } 1 - \delta

where NN is the total count in the window. With ε=10−4\varepsilon = 10^{-4} and a peak minute of N≈4.17N \approx 4.17 million clicks, the overestimate is at most about 417 clicks with probability 0.999. Sketches with the same dimensions and hash functions merge by adding cells, so the merger sums the per-task sketches. The Space-Saving algorithm (Metwally and colleagues, ICDT 2005) is the alternative when only the heavy hitters matter: it keeps a fixed number of counters and gives a guaranteed error bound per counter. Label these rankings approximate in the API.

7.7 How real systems do it

How real systems do it (paraphrased, not copied). Google's SIGMOD 2013 paper "Photon: Fault-tolerant and Scalable Joining of Continuous Data Streams" (Ananthanarayanan and colleagues) describes a system that joins ad click events with the search query events that led to them, across several datacenters at once. The authors state three guarantees: the joined output never contains a duplicate, most joinable events appear in real time, and every event is eventually joined exactly once. They report that it ran through degraded infrastructure and whole datacenter outages without manual steps, handled millions of events per minute at peak with average end-to-end latency under 10 seconds, and fed advertiser billing. The idea reused here is that a shared record of already-processed event IDs, kept consistent across workers, is what prevents double counting when processing is retried or runs in more than one place. The Apache Kafka design documentation explains the transactional pattern in 7.3: offsets are committed in the same transaction as the output, consumers read only committed records, and for external stores the offset is kept together with the output instead of using two-phase commit. Read the originals in 12.3.

8. Scalability and performance

ConcernApproachLimit before redesign
Click rateStateless click service, async bufferLoad balancer and Kafka brokers, far above 69 000/s
Hot ad (one viral ad)One partition per ad caps one ad at one task's rate; pre-aggregate per click-service instance for 1 s before producing if neededExample: about 10 000 clicks/s for a single ad
Dedupe stateLocal keyed state with TTLExample: about 150 GB at 10x, still local
Replay speed4x task headroomMust catch up a 1-hour backlog in under 15 min
OLAP query loadPre-aggregated rows, sort key by ad and timeExample: 2 000 queries/s per replica set

Freshness budget for a minute window (target NFR-01, 60 s after window close):

StepBudget
Click buffer flush to Kafka1 s
Watermark delay30 s
Window emit and sink write5 s
OLAP visibility10 s
Headroom14 s

9. Reliability, security and operations

9.1 Failure modes

ComponentFailureDetectionBlast radiusMitigationRTO
Aggregation taskCrash or node lossConsumer lag per partitionAds on that task's partitions, counts delayedRestore from checkpoint, replay from offsets; no double count (7.3)Example: 2 min
Kafka brokerBroker downUnder-replicated partitionsNone with replication factor 3 and min.insync.replicas=2Leader electionExample: 30 s
Click bufferClick service instance dies with unflushed eventsIngest count vs. redirect countUp to 1 s of clicks on one instanceSmall flush interval; buffer on local disk for durabilityExample: 1 s of data at risk
OLAP storeRegion unavailableQuery error rateDashboards only, billing unaffectedReplica in second zone; stream sink retries idempotentlyExample: 15 min
ArchiverFalls behind or drops filesHourly raw count vs. Kafka offsetsBatch recount and billing for that hourBlock billing until counts match; replay from Kafka within 7 daysExample: 1 h
Bad deploy of aggregatorWrong countsReconciliation drift above 0.1%Every ad, provisional numbersRoll back, replay from last good savepoint, batch overwritesExample: 1 h
[Component][Failure][Signal][Scope][Mitigation][Time]

9.2 Security, privacy and abuse

ThreatControl
Forged or replayed click tokensHMAC-signed tokens with expiry; key rotation; replays share a click ID and are deduplicated
Click fraud and botsInvalid traffic flag from a separate service; flagged clicks counted separately, never billed
Personal data in click eventsKeep only coarse country and device class; no raw IP in the aggregation path; IP kept in a restricted fraud store with short retention
Advertisers reading other advertisers' dataQuery API scopes every request by advertiser_id from the caller's token
Tampering with billing totalsBilling DB writes only from the reconciler; versioned, append-only adjustments, audited
[Threat][Control]

Data protection law applies to click data that can identify a person. For example, the EU GDPR storage-limitation principle (Article 5(1)(e)) and the UK GDPR equivalent require that identifiable data is not kept longer than needed. This is a design note, not legal advice; check the current official text and a qualified professional for your jurisdictions.

9.3 Observability

SignalMetric or logAlert threshold
Consumer lagSeconds behind per partitionAbove 120 s for 5 min
Watermark lagNow minus watermarkAbove 90 s
Late eventsShare of clicks into side outputAbove 0.5% of an hour
Duplicate rateShare of clicks dropped by dedupeDoubling vs. 7-day baseline
Reconciliation driftMax per-ad difference per hourAbove 0.1%
Checkpoint durationp99 per jobAbove 50% of the checkpoint interval

9.4 Cost estimate

ItemUnit costQuantityMonthly cost
Kafka brokers[$/broker-month]Example: 6[$]
Stream processing workers[$/vCPU-month]Example: 32 tasks on 8 workers[$]
OLAP store[$/node-month]Example: 6 nodes, 7 TB[$]
Object storage for raw archive[$/TB-month]24 TB[$]
Batch recount[$/vCPU-hour][hours/month][$]
Total[$]

10. Trade-offs and alternatives

DecisionOptionsChosenWhyWhat we give up
ArchitectureBatch only; stream only (kappa); stream plus batch reconciliationStream plus batch reconciliationFresh dashboards and defensible billingTwo code paths to keep in agreement
Counting storeIncrement counters in a database; aggregate in stream and upsertAggregate in stream, upsert absolute valuesNo hot rows, replay-safeResults appear per window, not per click
DedupeBloom filter; keyed exact state; database unique constraintKeyed exact stateNo false drops, no remote callAbout 15 GB of state at peak
Top-KExact everywhere; sketch everywhereExact for ads, sketch for publishersExactness where cheap, bounded state where widePublisher rankings are approximate
Late dataDrop; long lateness in stream; batch correction10 min in stream, batch for the restBounded stateStragglers appear only after the batch run
What changes at 10x / 0.1x10x (about 694 000 clicks/s at peak): about 70 tasks within the 128 partitions, dedupe state about 150 GB, pre-aggregate hot ads in the click service, and consider sketch-based top-K for countries too. 0.1x (about 6 900/s): one Kafka cluster of 3 small brokers and a single stream worker, or even a database with a unique index on click_id and a nightly recountReview at each doubling of peak clicksState and replay time grow first, not bandwidth[Note]
[Decision][Options][Chosen][Why][Cost]

11. Delivery plan and risks

11.1 Milestones

MilestoneScopeExit criterionTarget date
M1Click service, Kafka topic, archiverRaw counts match the legacy log job for 14 daysYYYY-MM-DD
M2Stream aggregator and OLAP store in shadowHourly drift at or below 0.1% for 14 daysYYYY-MM-DD
M3Dashboards and top-K live, labelled provisionalFreshness target met for 7 daysYYYY-MM-DD
M4Billing from reconciled totalsFinance sign-off after one parallel billing monthYYYY-MM-DD

11.2 Rollout and rollback

[Example: Run the new pipeline beside the legacy batch job for a full billing month and compare invoices line by line. Dashboards switch first; billing switches only after finance approves the parallel run. Rollback for the stream path is a redeploy from the last savepoint; billing can fall back to the legacy job until it is decommissioned.]

11.3 Risks

IDRiskLikelihoodImpactMitigationOwner
R-01Example: Stream and batch logic diverge (different dedupe or IVT rules)MediumHighShared rule library and the same test fixtures for both paths[@name]
R-02Example: A viral ad overloads one partitionMediumMediumClick-service pre-aggregation, alert on per-partition lag[@name]
R-03Example: Advertisers dispute provisional numbersHighMediumLabel provisional, show reconciliation status per day[@name]
R-04Example: Archive gap discovered after the Kafka retention windowLowHighArchive-vs-offset check every hour, 7-day retention[@name]
[R-nn][Risk][L / M / H][L / M / H][Mitigation][@name]

12. Appendices

12.1 Open questions

IDQuestionOwnerDue
OQ-01Example: Should invalid-traffic decisions made after invoicing trigger credits automatically?[@name]YYYY-MM-DD
OQ-02Example: Do advertisers need minute data beyond 30 days?[@name]YYYY-MM-DD
[OQ-nn][Question][@name]YYYY-MM-DD

12.2 Glossary

TermMeaning
Click IDUnique, signed identifier of one click, minted when the ad is served
Event timeWhen the click happened, carried in the event
WatermarkThe processor's estimate that no older events are still to come
Allowed latenessHow long a window accepts late events after the watermark passes
Exactly-once (effective)Each accepted event changes published results once, despite retries and replays
ReconciliationComparing streaming counts with a batch recount and correcting the difference
Count-Min sketchFixed-size table of counters that overestimates counts by a bounded amount
Heavy hitterA key whose count is a large share of the total
Invalid traffic (IVT)Clicks judged not to come from genuine user interest
[Term][Meaning]

12.3 References

ReferenceUse
Cormode G., Muthukrishnan S., "An improved data stream summary: the count-min sketch and its applications", Journal of Algorithms 55(1), pp. 58-75, April 2005, doi:10.1016/j.jalgor.2003.12.001Sketch dimensions and error bound in 3.6 and 7.6
Metwally A., Agrawal D., El Abbadi A., "Efficient Computation of Frequent and Top-k Elements in Data Streams", ICDT 2005, LNCS, pp. 398-412, doi:10.1007/978-3-540-30570-5_27Space-Saving alternative in 7.6
Ananthanarayanan R. et al., "Photon: Fault-tolerant and Scalable Joining of Continuous Data Streams", SIGMOD 2013, pp. 577-588, doi:10.1145/2463676.24652727.7
Apache Kafka documentation, Design: message delivery semantics, idempotent producer and transactions since 0.11.0.07.2, 7.3
Stream Processing Engine System Design (system-design/design-stream-processing-engine)Windows, watermarks, checkpoints
Message Queue System Design (system-design/design-message-queue)Partitioning and retention of the clicks topic
Fraud Detection System Design (system-design/design-fraud-detection)Invalid traffic flag
Gaming Leaderboard System Design (system-design/design-gaming-leaderboard)Exact ranking patterns
Change Data Capture Pipeline Design (system-design/design-cdc-pipeline)Exactly-once delivery into downstream stores
Back-of-Envelope Estimation and Capacity Model (system-design/back-of-envelope-estimation)Method for section 3

Standard and licence note

Based on the published algorithms and documentation cited in 12.3: the Count-Min sketch (Cormode and Muthukrishnan, 2005), the Space-Saving algorithm (Metwally and colleagues, 2005) and the Apache Kafka delivery semantics documentation. The Photon paper is paraphrased in 7.7 only; no text, figures or code were copied, and every number in this template is an illustrative example computed for this design. Check the current Kafka documentation for your version before relying on a configuration name. The note on data protection in 9.2 is not legal advice. Original text written for this collection. Template licence: MIT.

Related templates