Someone Else's Server

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
Whatnot

Problem

We run a live shopping marketplace and settle every order through an external payments vendor that exposes its records over a paginated REST API we do not control. Build the business-critical pipeline that pulls this data on a schedule and picks up from the last page it landed when a run dies partway, keeps ingesting when the vendor rate-limits or returns errors on some pages, and only lets clean, deduplicated records reach the finance warehouse that reconciliation and payouts read from. The vendor occasionally resends the same record, and its daily volume runs into the low millions.

Business requirements

Worked solution and explanation

Why this problem exists in real interviews

This looks like an ingestion problem but it is really a trust problem: you are building on a server you do not control, that rate-limits you, drops the occasional page, and resends the same settlement record more than once, and the output feeds payouts. The trap is the tidy daily pull: schedule a job, page through the vendor API, write to the warehouse, done. That design is correct only on the days the vendor behaves. The day it 429s in the middle of a run, your job either restarts from zero and hammers the vendor or resumes blindly and skips the middle, and the day it resends a batch, finance pays a seller twice. At the low-millions-a-day volume here, a duplicate rate of even a fraction of a percent is thousands of double payments.

So the real skill being probed is designing for at-least-once delivery from an unreliable external source: resumable ingestion, idempotent landing on a stable key, a quality gate before the money-critical consumer, and a dead-letter path so one bad page never blinds finance for the whole day. Every requirement traces back to the single fact that you do not own the other end of the wire.


Break down the requirements

Step 1: Pin the delivery contract before anything else

Ask how the vendor paginates, how it fails, and whether it delivers at least once. The answers here (cursor pagination, 429s and 5xx per page, duplicate ids, a stable vendor_txn_id) decide the entire design. If you skip this, you design for a clean feed that does not exist.

Step 2: Make the pull resumable, not restartable

The orchestrator owns a cursor or watermark that advances only on a page fully landed. When a run dies at page 400 of 900, the next run resumes at 400. Per-page retry with exponential backoff absorbs the transient 429s and 5xx; a page that still fails after N attempts goes to the dead-letter store so the run does not stall on it.

Step 3: Land raw, then dedup on the stable id

Write the raw vendor payload immutably first, so any downstream bug is replayable without touching the vendor again. Then upsert into the deduplicated table keyed on vendor_txn_id, latest updated_at wins. This one decision is what turns at-least-once delivery into effectively-once state, and it is where duplicate payouts are prevented.

Step 4: Gate on quality before the warehouse, not after

Amount non-null and non-negative, currency in the allowed set, row count within a band of the trailing average. Failing rows are quarantined and a batch that fails the volume check does not promote. The warehouse reads from the gate, never from the raw zone, so reconciliation only ever sees validated data.


The reference architecture

An orchestrator schedules the pull and owns the cursor. An ingestion worker pages the vendor API with per-page retry and backoff, writing raw responses to an immutable landing zone and shipping exhausted-retry pages to a dead-letter store. A dedup/upsert step keyed on vendor_txn_id produces the effectively-once table. A quality gate validates schema, nulls, and volume, quarantining failures, and only its output is promoted to the finance warehouse that reconciliation and payouts read. Observability watches freshness against the deadline, failure rate, and dead-letter depth, and alerts the team before finance notices.

Resumable pull with per-page retry, immutable raw landing, dead-letter for stuck pages, dedup on the stable vendor id, and a quality gate that is the only thing feeding the finance warehouse.
  • vendor_api (source, External REST API, cursor paginated, ~3M records/day)
  • orchestrator (transform, Airflow, owns schedule + cursor); alerting: Run past freshness deadline
  • ingestion_worker (transform, Python, per-page retry + backoff); on error: dlq
  • raw_landing (storage, S3 raw zone, immutable); backfill: partition_overwrite
  • dead_letter (storage, S3 DLQ); alerting: DLQ depth above baseline
  • dedup_upsert (transform, Spark, upsert on vendor_txn_id); idempotency: upsert
  • quality_gate (quality gate, Great Expectations); on error: quarantine
  • finance_warehouse (storage, Snowflake); freshness: < 24h
  • reconciliation (consumer, Reconciliation + payouts)
  • observability (consumer, Datadog, freshness + DLQ alerts)
  • orchestrator -> ingestion_worker
  • vendor_api -> ingestion_worker
  • ingestion_worker -> raw_landing
  • ingestion_worker -> dead_letter
  • raw_landing -> dedup_upsert
  • dedup_upsert -> quality_gate
  • quality_gate -> finance_warehouse
  • finance_warehouse -> reconciliation
  • orchestrator -> observability
  • dead_letter -> observability
  • Schedule + pull: vendor_api, orchestrator, ingestion_worker
  • Land + recover: raw_landing, dead_letter, dedup_upsert
  • Validate + serve: quality_gate, finance_warehouse, reconciliation, observability

Common follow-up questions