advanced 2 min answer

A ride-hailing platform in the mould of Grab has a fact_orders table at order grain feeding 140 dashboards and 30 downstream models. Finance now needs line-item analysis, which requires order-line grain, and the new table will have about 4 times the rows. How do you move without breaking consumers?

grainfact tableschema migrationbackward compatibilityparitydual run
Show the full answer Hide the answer

The sequence

  1. Inventory consumers. Use query logs and lineage for 90 days to learn who reads fact_orders, which columns, and which aggregations. Some of the 140 dashboards are dead.
  2. Build fact_order_lines as a new table, never as an in-place change. Its grain is declared in one sentence and tested: one row per order line, unique on (order_id, line_number).
  3. Keep fact_orders alive as a derived table computed from the new fact by aggregating lines back to orders. Existing consumers keep working, and the two tables now cannot disagree because one is built from the other.
  4. Run a parity check: sum of revenue, order count and distinct customers by day between the old table and the derived one, with a tolerance of zero for counts and rounding-level for currency.
  5. Move consumers in waves, starting with those who need lines. Each moves by changing one reference, and the owner signs off on a parity query.
  6. Deprecate the old name with a date, a warning in query metadata and a final usage report; remove only when 30 days show zero reads.

Where data can diverge

Three places. Double counting when a consumer joins the line fact to order-level measures (shipping fee, discount) and sums them once per line: order-level amounts must be allocated to lines or kept in a separate order fact. Row-count inflation in any dashboard that counted rows as orders, which becomes lines. Late-arriving lines, where an order is updated after lines were added, leaving the derived table briefly inconsistent.

You would know through the parity check run daily during the dual period, plus a canary query that counts orders both ways.

The point of no return

Dropping the old table, or the first consumer that is rebuilt on lines and has its upstream history deleted. Before that, every step is reversible by pointing the consumer back.

The rollback at each stage

Steps 2 to 4 cost only storage. In step 5 rolling back is a one-line reference change per consumer, which is why waves of ten are safer than a big bang. After step 6 the table is restorable from snapshots for as long as retention allows, so state that period.

How long it really takes

The technical build is days. Moving 140 dashboards and 30 models is weeks to a quarter, dominated by owners who must test and approve; plan for the long tail of the last 10% of consumers, which often takes as long as the first 90%.

When this is overkill

A table with three known consumers, all owned by one team, can be changed in a single release with a fixed parity query. The ceremony pays for itself only when consumers are many and unknown.

Common weak answers

  • "Add a line_number column to the existing table." It changes the grain and silently inflates every sum.
  • "Announce a freeze date and switch." The long tail will break on the date.