Three Regions, One Finance Team

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
L6

Problem

Design the platform that turns payment events from our US, EU and APAC regions, each running its own infrastructure, into one consolidated daily view of global payment volume. The finance controller who signs off on that view every morning currently gets three regional silos and no single number to trust.

Payments from everywhere. One consistent report.

Business requirements

Worked solution and explanation

What this really is

This is a residency boundary problem wearing a reporting costume. Wiring three regional feeds into one warehouse is the easy part. The trap is centralizing first and enforcing residency at query time: by then raw EU card data already sits in a central store, which is an audit failure on day one. It also leaves finance staring at one consolidated number that hides which region never arrived.

Walk the requirements

Step 1: Ingest and aggregate in-region

Each region keeps its own Kafka log, and that log is where residency starts. eu_dedup_fx_aggregate removes duplicates and rolls payments up to totals on EU compute. What leaves it is a regional total per currency and day: no card numbers, no customer identifiers.

Step 2: Convert at the transaction's date

Each aggregator looks up the daily rate for the transaction's original date from fx_rate_history and keeps the original amount, the rate and the converted amount. Apply today's rate instead and every rebuild silently rewrites last quarter.

Step 3: Gate on completeness per region

region_completeness takes one input per region, compares expected and received counts, and tags each region 'complete', 'late' or 'missing'. The tag lands beside the totals in central_warehouse, so finance signs off knowing what is inside the number.

Step 4: Schedule against the morning read

daily_orchestrator runs the cycle once a day so the three regional totals and their status are in central_warehouse before finance opens finance_dashboard each morning.

The reference design

Raw events never leave their region. Only dated-FX aggregates pass the per-region `region_completeness` gate into the warehouse, on the daily cycle `daily_orchestrator` runs.
  • us_payments (source, Kafka)
  • eu_payments (source, Kafka)
  • apac_payments (source, Kafka)
  • fx_rate_history (source, PostgreSQL)
  • us_dedup_fx_aggregate (transform, Spark)
  • eu_dedup_fx_aggregate (transform, Spark)
  • apac_dedup_fx_aggregate (transform, Spark)
  • region_completeness (quality gate, custom)
  • daily_orchestrator (transform, Airflow)
  • central_warehouse (storage, warehouse)
  • finance_dashboard (consumer, dashboard)
  • us_payments -> us_dedup_fx_aggregate
  • eu_payments -> eu_dedup_fx_aggregate
  • apac_payments -> apac_dedup_fx_aggregate
  • fx_rate_history -> us_dedup_fx_aggregate
  • fx_rate_history -> eu_dedup_fx_aggregate
  • fx_rate_history -> apac_dedup_fx_aggregate
  • us_dedup_fx_aggregate -> region_completeness
  • eu_dedup_fx_aggregate -> region_completeness
  • apac_dedup_fx_aggregate -> region_completeness
  • region_completeness -> central_warehouse
  • daily_orchestrator -> region_completeness
  • central_warehouse -> finance_dashboard
  • Regional ingestion: us_payments, eu_payments, apac_payments, fx_rate_history
  • In-region aggregation: us_dedup_fx_aggregate, eu_dedup_fx_aggregate, apac_dedup_fx_aggregate
  • Reconciliation: region_completeness, daily_orchestrator
  • Central and reporting: central_warehouse, finance_dashboard
Centralize, then filter
Raw EU rows already sit in the central warehouse, and a filter on read does not change where they are stored. FX looked up at today's rate makes totals drift on every rebuild. A late region vanishes into one number.
Aggregate in-region, then ship
Only aggregates cross the border. The rate for the transaction's date is fixed on the row. A late region shows up as 'late' next to its total instead of hiding inside it.

Common follow-up questions