Design a pipeline for 40M mobile events a day, served to analysts in Snowflake within an hour. The SDK retries on network failure and cannot guarantee a unique event_id per send. Which single line of the design makes it correct?
The architecture and the run — work it out before reading on
Sources
mobile SDK
40M events/day. Buffers offline, retries on failure, no unique event_id guarantee.
Landing
Kafka: events
7-day retention, partitioned by session_id.
idempotentraw_pageview (Delta)
Append-only. Every record as received, partitioned by ingest date.
idempotentTransform
fct_pageview (Delta)
MERGE on event_id, so a redelivery is a no-op.
idempotentServing
Snowflake / analysts
Complete by 10 past the hour, 99% of days.
- mobile SDK → Kafka: eventsat-least-once~2sThe SDK retries, so the same event can be produced twice.
- Kafka: events → raw_pageview (Delta)at-least-once~5 minStructured Streaming commits offsets after the write, so a restart replays.
- raw_pageview (Delta) → fct_pageview (Delta)at-least-oncehourlyReads with an overlap on purpose; the MERGE absorbs it.
- fct_pageview (Delta) → Snowflake / analystsexactly-onceimmediateExternal table, so publishing is a metadata operation.
fct_pageview starts empty. stg_pageview holds one delivery at a time.
The load — run once per delivery
INSERT INTO fct_pageview SELECT event_id, session_id, path FROM stg_pageview s WHERE NOT EXISTS (SELECT 1 FROM fct_pageview f WHERE f.event_id = s.event_id);
The deliveries, in order
1the consumer processes a batch
stg_pageview — delivery 1 event_id session_id path ev1 s1 /home ev2 s1 /pricing ev3 s2 /home 2the consumer restarts and the broker redelivers it
Same hop, same redelivery. The only thing that changed is the write.
stg_pageview — delivery 2 event_id session_id path ev1 s1 /home ev2 s1 /pricing ev3 s2 /home
Why they ask this
The standard opener for a data engineering system design round. The shape is common property; what is graded is whether you can point at the one decision the correctness of the whole thing rests on.
Say this
Kafka for capture and replay, Delta for an append-only raw layer, a MERGE on event_id into the fact, Snowflake reading it. The line that makes it correct is the MERGE: every hop above it is at-least-once, so the write has to absorb a repeat.
The reasoning
Take the hops in order. The SDK retries, so the same event reaches Kafka more than once. Structured Streaming commits offsets after writing, so a restart replays the micro-batch. The hourly load into the fact reads with a deliberate overlap so nothing falls through the boundary. Every one of those is at-least-once, and none is a fault — it is simply what these components offer.
So duplicates arrive at the fact table routinely, and the design does not try to prevent them upstream because it cannot. It absorbs them at the write: MERGE INTO fct_pageview USING ... ON t.event_id = s.event_id. The run below delivers the same batch twice and the table is left identical, and equal to a full recompute over everything delivered.
The missing event_id guarantee is the part to raise unprompted, because it is the assumption the MERGE depends on. If the client cannot mint a stable id, derive one deterministically from the fields that identify the event — session id, event type, client timestamp — and hash them. A random UUID minted at send time changes on every retry and defeats the entire design, which is the specific mistake worth naming.
The run — 2 deliveries, in order replay changed nothing · matches a full refresh, verified
The same batch delivered twice, into the keyed MERGE.
1after the consumer processes a batch
fct_pageview event_id session_id path ev1 s1 /home ev2 s1 /pricing ev3 s2 /home 2after the consumer restarts and the broker redelivers it
fct_pageview event_id session_id path ev1 s1 /home ev2 s1 /pricing ev3 s2 /home
And the same table, recomputed from scratch over everything ever delivered
SELECT DISTINCT event_id, session_id, path FROM delivered ORDER BY event_id
| event_id | session_id | path |
|---|---|---|
| ev1 | s1 | /home |
| ev2 | s1 | /pricing |
| ev3 | s2 | /home |
The answer most people give
"Enable exactly-once semantics in Kafka, then duplicates cannot happen." That covers Kafka-to-Kafka processing. Your consumer writes to Delta, which is a different system, so the guarantee has ended before the row reaches the table you care about.
They’ll ask next
The SDK cannot mint a stable event_id at all. What do you key the MERGE on?
