Move a regional payments dashboard to a second query engine while preserving its keys, exact monetary totals and time-bucket behavior.
Project: migrate a dashboard with shadow reads
Freeze the input and contract
Pin a source generation containing 470 payments across three regions, null customer references, a correction and events close to a UTC midnight boundary. State the dashboard grain, minor-unit money representation, permitted filters and expected report time zone. Save both engine session settings. Semantic probes must pass before a production-sized shadow run.
Translate and run unseen
Translate the detail and aggregate queries to the target dialect. Execute them under a separate shadow identity, without displaying target answers to users. Attach source and target generation IDs to each result. Limit replay to a fixed concurrency and scan budget. Compare stable keys and exact cents totals, then measure p50 and p95 latency for the same filter shapes.
Plant three faults
Make the target treat one null reference as zero, shift one event into the next local day and duplicate a corrected payment. Show that row-count-only and grand-total-only checks each miss at least one planted fault. The discrepancy report must identify affected keys, categories and query revisions. Shadow triage should produce a small reproducible fixture for each cause.
Gate and canary
Repair the translation, replay multiple intervals and require exact key coverage and exact cents totals for critical queries. Move only one dashboard cohort to the new engine. Observe freshness and latency while the old route remains available. If one later correction diverges, roll the cohort back to the prior pointer and keep the failed candidate for diagnosis.
Deliver evidence
Submit the contract, source generations, SQL revisions, semantic fixtures, mismatch reports before and after repair, spend limit, query latency and canary rollback trace. Include one unresolved scenario and mark it as blocking; a cutover gate should not silently ignore a missing interval. The final dashboard result should be reproducible from the pinned manifest and comparison inputs.
Implementation
from collections import Counter
source_payments = [("pay-47", "west", 2375), ("pay-48", "east", 6400),
("pay-49", "west", -125)]
target_payments = [("pay-49", "west", -125), ("pay-47", "west", 2375),
("pay-48", "east", 6400)]
def reconcile(records):
keys = Counter(payment_id for payment_id, _, _ in records)
totals = {}
for payment_id, region, cents in records:
totals[region] = totals.get(region, 0) + cents
return keys, totals
assert reconcile(source_payments) == reconcile(target_payments)
assert reconcile(source_payments)[1] == {"west": 2250, "east": 6400}Performance and operating cost
The reference reconciliation uses O(N) expected time and O(K + G) space for N rows, K distinct payment keys and G regions. Real shadow reads add two engine scans, comparison storage and a bounded canary period. Partitioned hashes can reduce detailed comparisons, but every mismatch must be resolved against pinned row evidence before a critical dashboard is moved.
Common Mistakes
- Do not expose the shadow result before its gate passes.
- Do not accept aggregate totals without checking duplicate and missing payment keys.
- Do not lose the old routing pointer before the canary interval ends.
