One Bill Across Three Clouds

A medium 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
medium
Seniority
L5

Problem

For thousands of enterprise customers, AWS sends hourly CSV cost reports, Azure a daily JSON export, and GCP a near-real-time stream, each in its own schema. How would you design a pipeline that unifies these into a consistent cost analytics layer and alerts our account managers when any managed account is trending toward a budget breach?

AWS, Azure, GCP. Three bills. One truth.

Business requirements

Worked solution and explanation

Normalize early, project daily

This is a schema-reconciliation problem dressed up as a billing dashboard. Anyone can land three feeds and draw a chart. What separates candidates is where the three meanings of 'cost' get merged: before query time, in one canonical record, or never. Union three provider tables in BI and the total will not match the invoices. Append a re-delivered corrected file and that period's line items double. Check budgets at month close and the customer learns about the breach from the bill.

Walk the requirements

Step 1: Land every provider raw before touching it

All three feeds, the hourly CSVs, the daily JSON export and the streaming feed, write unchanged into one retained lake. That is what lets you rebuild the unified view after a mapping bug or a provider correction, and the streaming feed gets the same replay story as the files.

Step 2: Normalize once into one canonical record

A single Spark job reads the lake and maps each provider's columns into one schema: account, resource, period, original amount, original currency, unified amount, FX rate used. 'Total spend across clouds' becomes one sum that reconciles, because the differences in what each cost column means were resolved here and not in a dashboard.

Step 3: Overwrite the period on load

The write into the warehouse replaces the partition keyed on provider, account and period. Re-running the same file is harmless and a corrected file swaps in atomically, so the load is idempotent and corrections never leave duplicates.

Step 4: Project against the budget every day

A dbt model reads canonical spend, computes month-to-date and extrapolates the run rate to month end. When the projection crosses the account's budget, an alert goes out with days left to act. It sits on its own branch, separate from the dashboard, so reporting stays a read path and detection stays a job.

Raw payloads are retained in the lake, one normalizer feeds one canonical warehouse, loads overwrite by provider, account and period, and a separate daily projection drives the alerts.
  • hourly csv cost reports (source)
  • daily json cost export (source)
  • streaming cost feed (source, Kafka)
  • raw billing lake (storage)
  • schema normalizer (transform, Spark)
  • canonical cost warehouse (storage); idempotency: delete_write
  • budget projection (transform, dbt)
  • cost dashboard (consumer)
  • budget alerts (consumer)
  • hourly csv cost reports -> raw billing lake
  • daily json cost export -> raw billing lake
  • streaming cost feed -> raw billing lake
  • raw billing lake -> schema normalizer
  • schema normalizer -> canonical cost warehouse
  • canonical cost warehouse -> budget projection
  • canonical cost warehouse -> cost dashboard
  • budget projection -> budget alerts
  • Sources + raw landing: hourly csv cost reports, daily json cost export, streaming cost feed, raw billing lake
  • Normalize + canonical: schema normalizer, canonical cost warehouse
  • Budget + consumers: budget projection, cost dashboard, budget alerts
Union in BI
Three tables with columns that only look alike. The total drifts from the invoices, every new provider means new dashboard logic, and a correction needs a manual cleanup.
Canonical first
One schema with original and unified amounts on every row. The dashboard and the projection read the same numbers, and a new provider only adds a mapping inside the normalizer.

Common follow-up questions