A Clean Number for Every Merchant

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

Our payments platform generates a daily log containing every transaction across all merchants. The finance and BI teams need a clean merchant-level summary - total volume, transaction count, average amount - but the raw logs have duplicates, schema inconsistencies, and no restart safety. Design a batch pipeline that reliably produces this summary.

Raw payment logs in. Clean merchant summaries out.

Business requirements

Worked solution and explanation

What this really is

This is an idempotency problem dressed up as a daily rollup. Anyone can draw source, Spark, warehouse, dashboard. What separates candidates is whether four requirements hold at the same time: the 7am deadline, dedup of gateway retries, a home for bad rows, and reruns that land on identical numbers. Solve them one at a time and they fight each other. Pair an append-style write with a dedup that keeps whichever copy arrived first, and the operator's rerun moves a merchant's total by a few thousand dollars. After that, finance stops trusting the warehouse.

Walk the requirements

Step 1: Put an orchestrator on the deadline

Airflow owns the daily DAG and its retries, and it alerts while there is still time to act if the run threatens 7am. Without it, finance discovers the missed window when they open the dashboard. The summary lands in the warehouse finance already reads.

Step 2: Dedup on payment_id before aggregating

A retried event has to collapse to one row whatever order the copies arrive in. Once copies are summed into a merchant total, you cannot take them back out.

Step 3: Quarantine bad rows, keep the rest moving

Rows missing merchant or amount go to a quarantine table along with the rejection reason. Valid rows flow on to aggregation. Failing the whole run over one bad row blocks finance. Dropping bad rows silently makes the totals drift away from the gateway's.

Step 4: Replace the date, never append to it

Build the day in staging, then delete that date from the summary and write the staged rows in its place. Whichever stage failed, rerunning the date produces the same end state.

The reference design

Dedup on a stable id before aggregation, quarantine instead of drop, and a delete-then-write of each date so every rerun matches a clean run.
  • transaction log (source, object storage)
  • orchestrator (transform, Airflow); alerting: page if run at risk of missing 7am
  • dedup step (transform, Spark)
  • row validation (quality gate)
  • quarantine table (storage, warehouse)
  • aggregator (transform, Spark)
  • merchant summary (storage, warehouse); idempotency: delete_write
  • finance team (consumer, dashboard)
  • transaction log -> dedup step
  • orchestrator -> dedup step
  • dedup step -> row validation
  • row validation -> quarantine table
  • row validation -> aggregator
  • aggregator -> merchant summary
  • merchant summary -> finance team
  • quarantine table -> finance team
Append the day
A failed run leaves half a day in the summary. The rerun appends a second copy on top, and a merchant's total now depends on where the first run died.
Replace the day
Staging builds the full day, then the date is deleted and rewritten in one step. Whether this is the first run or the third, the date ends up holding the same rows.

Common follow-up questions