Treat cutover as an evidence-backed, reversible release.
Build a Migration Acceptance Plan Covering Data, Query Results, Performance, Cost, Security, and Rollback
Build a migration acceptance gate spanning data, queries, performance, cost, security, and rollback.
Learning outcomes
Build an acceptance matrix spanning data, history, query results, performance, cost, security, SLOs, consumers, and rollback.
Use explicit pass/fail evidence rather than subjective “looks good” cutover decisions.
Separate measured local performance from modeled cost assumptions.
Execute and verify a reversible routing cutover.
Define retirement criteria for the legacy EDW only after rollback risk is acceptably reduced.
Chapter 29 begins from the governed AtlasMart state through
Chapter 28:
10 current paid lines, 8 orders, 12 units, 820 USD gross
revenue, 495 USD cost, and 325 USD gross profit, with source waterline 208. The sales fact
grain remains one paid order line;
gross_revenue_usd.v1 remains paid line amount in
USD under its governed filters and UTC time semantics. A
platform migration is not permission to redefine those
contracts.
The mandatory lab is free/local/synthetic and was executed with Python 3.13.5 and SQLite 3.46.1. SQLite supplies tables, views, transactions, UPSERT and query-plan evidence, but it does not implement stored procedures, cloud roles, CDC connectors, or managed-warehouse cost controls. Stored-procedure behavior and access roles are therefore represented as explicit metadata/policy fixtures, while SQL correctness and restart/idempotency behavior are executed locally. Time zone is UTC; no production credentials or personal data are used.
1. Problem frame: cutover is a release decision
AtlasMart has discovered hidden logic, chosen a mixed replatform/refactor strategy, backfilled history, replayed the CDC boundary, and preserved metric v1. None of those steps alone authorizes cutover. The final decision is a release gate with evidence across all consumer-critical dimensions. A team that validates only data can regress performance or security; a team that validates only dashboards can miss historical drift.
2. Acceptance matrix
| Dimension | Fixture acceptance evidence | Pass condition |
|---|---|---|
| Atomic controls | 10 / 8 / 12 / 820 / 495 / 325 | exact match |
| Historical fidelity | six daily control rows + SHA-256 | daily rows exact |
| CDC/restart | E207/E208 applied once; duplicate replay ignored | state unchanged after replay |
| Metric semantics | gross_revenue_usd.v1 old/new | 820 = 820 |
| Security | finance report reader cannot query raw sales_line | no bypass |
| Performance | same 180k-row query and result hash | new p95 <= defined threshold |
| Cost | explicit synthetic rate model | within approved model threshold |
| SLA/consumer | critical reports and owners inventoried | acknowledged and smoke-tested |
| Rollback | route modern -> injected failure -> legacy | tested before retirement |
3. Measured performance: same result before speed
The executed benchmark holds data, SQL semantics, warm-up
policy, cache mode, and concurrency constant. The old table has
no supporting index; the modern fixture has
(product_id, order_date). SQLite's plan evidence
changes from SCAN old_fact to
SEARCH new_fact USING INDEX idx_new_product_date.
The result hash is identical. On this generation environment,
p95 was 8.0677 ms old versus
0.1669 ms new. Timing values are
environment-dependent; the reproducible obligation is same
result plus disclosed method.
Environment: Python 3.13.5 / SQLite 3.46.1Dataset: 180,000 deterministic benchmark rowsCache: warm SQLite page cache after 8 warmupsConcurrency: 1Legacy p50/p95/p99: 6.847 / 8.0677 / 8.9842 msModern p50/p95/p99: 0.0983 / 0.1669 / 0.2661 msLegacy plan: SCAN old_factModern plan: SEARCH new_fact USING INDEX idx_new_product_date ...Result checksum: bfb516aa49a62401ec32c3a301f5bd2bb08054ce768d8195bd067953fc4fdc77
4. Cost comparison: assumptions are data too
The cost gate uses fictional teaching rates, not current vendor pricing: old fixed compute is modeled as 730 hours × 0.50 USD/h plus storage/egress; modern compute is 180 hours × 1.25 USD/h plus its storage/egress assumptions. That yields 370.50 USD versus 232.25 USD in the model. The only valid conclusion is “under these stated assumptions.” A real migration must substitute current region, pricing mode, discounts, storage class, egress path, concurrency, and observed workload.
5. Security acceptance: intended view plus effective privilege
The initial modern policy is deliberately unsafe:
finance_report_reader can query both the governed
report and raw sales_line. The gate fails even
though the dashboard itself is correct. After repair, the role
can query modern_finance_report but direct raw
access is denied. This reuses the Chapter 22 principle that
secure views are ineffective when a bypass path remains open.
6. Rollback is an executable path, not a document heading
{ "before_cutover": "legacy", "after_acceptance": "modern", "after_injected_smoke_failure": "legacy", "reason": "rollback simulation only; both data copies retained"}
Routing rollback does not undo source events. It restores consumers to a known-good serving surface while the new platform is diagnosed. Therefore the migration plan must state which system continues ingesting, how dual-load divergence is prevented, and how a second cutover will reconcile the interval.
7. Controlled failure: cut over with no rollback criterion
Switch every consumer Friday evening, immediately stop the legacy load to save money, and define rollback only if someone complains.
Diagnosis: the team has removed its known-good state before the highest-risk observation period. A security regression or history defect discovered later requires emergency reconstruction rather than routing rollback.
Repair: predefine rollback triggers, keep legacy data/load capability for an evidence-based observation window, rehearse route reversal, and require a separate retirement decision after cutover stability is demonstrated.
8. Executed acceptance gate
acceptance = { "data_controls_match": True, "daily_history_match": True, "metric_v1_match": True, "security_no_finance_raw_bypass": True, "performance_result_match": True, "performance_p95_not_worse_than_legacy_125pct": True, "modeled_cost_not_over_legacy_125pct": True, "cdc_replay_idempotent": True, "rollback_route_tested": True,}assert all(acceptance.values())print("acceptance all pass: True")
The 1.25 multipliers are fixture policy, not universal migration thresholds. Production thresholds belong to business and platform owners and must reflect SLOs, risk, budget, and consumer criticality.
9. Legacy retirement criteria
Cutover and retirement are separate decisions. Retirement requires evidence that all consumers moved, required history is preserved, audit/retention obligations are satisfied, rollback dependence has ended, runbooks/ownership transferred, exports and scheduled jobs no longer depend on the old system, and secrets/roles/infrastructure can be decommissioned safely. Keep migration evidence according to organizational policy even after the legacy runtime is removed.
10. Full lab run and expected output
python ch29_lab_script.py# Key expected semantic lines (timings vary by machine):# legacy physical: (11, 9, 13, 860, 515, 345)# legacy governed: (10, 8, 12, 820, 495, 325)# naive modern: (11, 9, 13, 860, 515, 345)# corrected modern:(10, 8, 12, 820, 495, 325)# cdc: ['applied','applied','duplicate_ignored','duplicate_ignored']# acceptance all pass: True# route: legacy -> modern -> legacy (injected rollback test)
The complete executable fixture used to produce the chapter evidence is standard-library Python plus SQLite. Recreate it from the code patterns in this chapter or your course workspace; do not treat the measured latency numbers above as fixed expected output on another machine.
11. Verification checklist
- Data and history parity are exact at declared grains.
- Metric versions and critical query results are unchanged unless separately versioned.
- Performance evidence uses equivalent data/query/cache/concurrency conditions and preserves results.
- Cost assumptions are explicit and replaceable with current real pricing.
- Effective access has no new raw/staging bypass path.
- Rollback was executed before legacy retirement.
- Consumer owners know the cutover and incident communication path.
All destructive actions target only a disposable
atlasmart_ch29_lab directory. Never point these
commands at a production warehouse. Reset with
python -c "import shutil; shutil.rmtree('atlasmart_ch29_lab',
ignore_errors=True)"
and rerun the fixture from a clean directory.
12. Production judgment and bridge to Chapter 30
Modernization succeeds when the new platform can demonstrate preserved meaning, controlled change, adequate service, secure access, and reversible operations. A faster query or successful table copy is supporting evidence, not the decision itself. Chapter 30 now combines the entire course into a production capstone: business processes and bus matrix, dimensional models and history, incremental loading, quality/orchestration, physical design, semantic metrics, marts, security, testing, observability, cloud/lakehouse boundaries, incident recovery, cost evidence, ownership, and an evolution roadmap.
Knowledge check
What is the difference between cutover and legacy retirement?
Show answer
Cutover routes consumers to the new platform. Retirement removes the old capability only after observation, consumer migration, retention/audit needs, and rollback dependence are resolved.
Why must performance acceptance include result equality?
Show answer
A faster query that changes filters, joins, grain, or approximation semantics is not a valid optimization of the same workload.
Are the 370.50 and 232.25 USD values vendor price comparisons?
Show answer
No. They are explicit fictional teaching assumptions used to demonstrate cost-model discipline. Real decisions must use current region/workload/contract pricing.
Why test a security bypass during migration acceptance?
Show answer
Because a correct governed view does not protect data if the migrated role can directly query raw or staging tables and bypass the policy.
What makes rollback credible?
Show answer
A rehearsed route/state transition with known-good data and ongoing ingestion/reconciliation rules—not merely a sentence saying “we can roll back.”
Authoritative references
- SQLite — Transactions for the local atomicity model used in the executable harness.
- SQLite — UPSERT for the local idempotent replay example.
- SQLite — EXPLAIN QUERY PLAN for the local plan evidence; its output format is explicitly not a stable application API.
- Kimball Group — DW/BI resources for dimensional modeling, business process/grain discipline, and lifecycle-oriented warehouse delivery.
- Big Data Academy — Data Warehousing and Dimensional Modeling curriculum for this course's stable AtlasMart contracts and chapter sequence.