Under One Roof

A hard Pipeline Design interview practice problem on DataDriven. Design the pipeline on the canvas: sources, transforms, storage and checks, then get feedback on it.

Domain
Pipeline Design
Difficulty
hard
Seniority
L7

Problem

We run a global retailer with physical stores and e-commerce, and four teams pull the same sales from one long-retention data lake: merchandising watches live performance and needs each transaction within minutes, finance reconciles daily totals, data science trains weekly models, and supply chain fires reorder triggers. Card numbers fall under PCI and can't be written to the lake at all, and both the point-of-sale stream and the e-commerce stream retry, so the same sale can arrive twice. Design the architecture from those two event streams through to each of the four consumers.

Business requirements

Worked solution and explanation

What this really is

This is a fan-out problem dressed up as retail analytics. You have one sales stream and four query shapes with four cadences, so you need one clean stream feeding several stores. Anyone can draw Kafka into a lake. What separates candidates is where they put the two irreversible steps. Tokenization and dedup have to happen once, at ingest, before any storage node. Put them in the lake as a masking view and the auditor finds raw PANs in the underlying files. Dedup per consumer and finance's daily total drifts away from merchandising's live count by exactly the retries.

Walk the requirements

Step 1: Put the tokenizer ahead of every store

The queue may carry raw card data, but the first storage node may not. Swap the PAN for a token in-stream, so PCI scope shrinks to the tokenizer and its vault instead of every table the sale ever touches.

Step 2: Dedup on transaction_id before fan-out

POS and e-commerce both retry, so both must converge on the same dedup step. Pair it with an idempotent upsert on the same key, so a replay after a crash rewrites a row instead of adding one.

Step 3: Give merchandising a real-time path

dedup_stream runs on Flink at real-time freshness and writes straight into merchandising_store. The lake is not on this path. Minutes-fresh dashboards cannot wait for a daily partition to land.

Step 4: Route each team to its own shape

Finance reads a daily Snowflake build from the lake. Data science reads stable lake history for weekly training. Supply chain gets a point-lookup store keyed on SKU for reorder triggers. The lake is the shared long-retention anchor, not the table every team queries.

The reference design

Tokenize and dedup once in-stream, then fan out: live stores for merchandising and supply chain, the `S3` lake for data science, and a daily warehouse for finance.
  • pos_terminals (source, Kafka)
  • ecommerce_orders (source, Kafka)
  • tokenizer (transform, Flink); on error: alert
  • dedup_stream (transform, Flink); freshness: real-time; idempotency: upsert
  • sales_lake (storage, S3); backfill: partition_overwrite
  • merchandising_store (storage, PostgreSQL); freshness: < 1min
  • finance_warehouse (storage, Snowflake); freshness: < 24h
  • inventory_triggers (storage, PostgreSQL); freshness: < 1min
  • merchandising_team (consumer, Grafana); freshness: < 1min
  • finance_team (consumer, Tableau); freshness: < 24h
  • data_science (consumer, Jupyter); freshness: < 24h
  • supply_chain (consumer, API); freshness: < 1min
  • pos_terminals -> tokenizer
  • ecommerce_orders -> tokenizer
  • tokenizer -> dedup_stream
  • dedup_stream -> sales_lake
  • dedup_stream -> merchandising_store
  • dedup_stream -> inventory_triggers
  • sales_lake -> finance_warehouse
  • merchandising_store -> merchandising_team
  • finance_warehouse -> finance_team
  • sales_lake -> data_science
  • inventory_triggers -> supply_chain
  • Sources + tokenize + dedup: pos_terminals, ecommerce_orders, tokenizer, dedup_stream
  • Stores: sales_lake, merchandising_store, finance_warehouse, inventory_triggers
  • Consumers: merchandising_team, finance_team, data_science, supply_chain
Dedup per consumer
Each store runs its own DISTINCT logic with its own window. Finance catches a late retry and merchandising does not, so the two totals never reconcile.
Dedup once at ingest
One step keyed on transaction_id feeds every store. Downstream upserts on the same key make replays harmless, and every view counts the same sale once.

Common follow-up questions