The Firehose and the Ledger

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
senior
Asked in
Sigmoid

Problem

We ingest payment events from a few hundred partner APIs, some pushing webhooks and some we poll on a schedule, totaling around 2 billion events a day. Operations needs a near-real-time view of transaction volume and error rates within seconds, while finance needs an exactly-once daily ledger that reconciles to the cent. Partners go down and resend the same events when they recover, so the platform has to retry, deduplicate, and never lose or double-count a transaction.

Business requirements

Worked solution and explanation

Why this problem exists in real interviews

This is two correctness budgets fed by one unreliable firehose, dressed up as a platform. Operations wants an approximate view in seconds; finance wants an exact ledger once a day. The trap is the single pipeline that aggregates everything into one table both teams read: too slow to feel live for ops, too approximate to bill on for finance, and the day a partner replays its backlog it double-counts the ledger. The skill probed is whether you split the budgets and put exactness in batch, not the stream.

The other half is the ingestion edge: a few hundred partner APIs, some webhook push, some polled, bursting and failing independently. Read partners directly and one slow partner backs up everyone while a recovering partner's replay floods the system. Those retries and day-late resends are exactly what the design must absorb without dropping or double-counting a cent.


Walk the requirements

Step 1: Buffer the ingestion edge before processing reads anything

Webhook pushes and scheduled pulls land in a durable queue first, so one bursty or offline partner is a non-event for everyone else: producers write at their pace, consumers drain at theirs. Read partner APIs straight into the stream and a single partner's recovery backlog stalls the platform during business hours.

Step 2: Land raw events immutably as the replay source

Every event is written as-is into the raw lake before any dedup. When your own logic is wrong, reprocess the day from the lake and overwrite the partition. Re-pulling hundreds of rate-limited partner APIs, some of which have aged the data out, is not a recovery plan.

Step 3: Give ops an approximate, seconds-fresh stream

A stream processor reads the queue and emits per-partner volume and error rate into a serving store the dashboard reads within seconds. Approximate is the budget: a transaction counted twice for a few seconds during a retry is invisible on a trend line. Put the exact ledger logic here and the view lags until ops stops trusting it.

Step 4: Make exactness a property of the daily batch, on a stable id

A batch job reads the day's raw events and deduplicates on the partner-assigned transaction_id over the full day, because a resend can arrive a day late. The result is written idempotently into the warehouse: rerun the day, get the same ledger. This is the only place exactly-once holds, because the window is the whole day, not a short streaming window.


The shape that fits

One buffered, immutably-archived source feeds two paths: approximate-and-live for ops, exactly-once-and-daily for finance. The raw lake is the replay source when curation is wrong.
  • partner_apis (source, Webhook push + scheduled poll); parallelism: ~300 partners
  • ingestion_queue (queue, Kafka); parallelism: high-partition
  • raw_event_lake (storage, S3); backfill: partition_overwrite
  • ops_stream (transform, Flink); freshness: real-time
  • ops_serving_store (storage, Redis); freshness: < 1min
  • ops_dashboard (consumer, Grafana); freshness: < 1min
  • ledger_curation (transform, Spark); backfill: partition_overwrite; idempotency: staging_table
  • ledger_warehouse (storage, Snowflake); freshness: < 24h
  • finance_reporting (consumer, Tableau); freshness: < 24h
  • pipeline_monitoring (transform, Prometheus); alerting: Ingestion lag, queue depth, per-partner error and dedup rate
  • partner_apis -> ingestion_queue
  • ingestion_queue -> raw_event_lake
  • ingestion_queue -> ops_stream
  • ops_stream -> ops_serving_store
  • ops_serving_store -> ops_dashboard
  • raw_event_lake -> ledger_curation
  • ledger_curation -> ledger_warehouse
  • ledger_warehouse -> finance_reporting
  • ingestion_queue -> pipeline_monitoring
  • Ingest: partner_apis, ingestion_queue, raw_event_lake
  • Ops path (approximate, live): ops_stream, ops_serving_store, ops_dashboard
  • Finance path (exact, daily): ledger_curation, ledger_warehouse, finance_reporting
  • Cross-cutting: pipeline_monitoring
One pipeline both teams read
A single stream aggregates everything into one table. Ops sees lag because the table carries the heavier exactness logic; finance double-counts a day-late replay because the streaming dedup window is too short. Both teams unhappy, differently.
Two paths, one source
Ops reads a light streaming path tuned for seconds and approximate counts; finance reads a daily batch that dedups over the whole day and reconciles to the cent. Same raw events feed both; the budgets diverge after the lake.

Common follow-up questions