Migrating a legacy system incrementally: data authority, cutover, and rollback
Routing users to a replacement does not finish a data migration. Follow an order-system modernization through behavioral baselines, transfer of write ownership, business-level reconciliation, recovery after new writes, and retirement of the legacy system.
Start with a business boundary you can switch
Rebuilding every order-system feature before a final cutover weekend can hide undocumented behavior until late. A strangler fig approach routes between legacy and replacement components and moves functionality incrementally. AWS guidance describes using a routing layer to make that transition gradual.
Choose migration units using business and data dependencies, not screen URLs alone. Splitting one order's create, amend, and cancel paths between systems can lose updates. Route consistently by tenant or order ID, and account for reports and batches spanning those units.
Record current behavior and agree what will change
Screens and APIs miss nightly batches, CSV integrations, and manual corrections. Trace one order through input, reservation, shipment, cancellation, and billing. Inventory all readers and writers. Where behavior is undocumented, record representative outputs and database updates as tests.
- Include canceled orders, date boundaries, missing integration data, and duplicate requests. Confirm monetary, state, and ordering semantics with business owners.
- Normalize only irrelevant variation such as generated IDs and timestamps. Preserve meaningful differences; use anonymized or synthetic test data with appropriate access.
- A baseline records behavior rather than proving it correct. Agree the reason and expected result for intentional bug fixes.
Tabulate authority and write ownership at each stage
| Stage | Authoritative writes for the selected data | Reads and validation |
|---|---|---|
| Preparation | Legacy alone writes | Continue legacy operations; transfer initial data and changes |
| Shadow comparison | Legacy alone writes | Return legacy results; compare replacement reads separately |
| Pilot reads | Legacy alone writes | Route selected reads to replacement; measure lag and impact |
| Transfer ownership | Pause selected writes and drain in-flight legacy updates | Synchronize and reconcile to a defined position; fence legacy writes |
| Replacement operation | Replacement alone writes the selected data | Route reads there too; define any remaining legacy replication narrowly |
Consider change data capture (CDC) or an outbox for transfer. Row replication alone does not establish equivalent business meaning. Define state mapping, deletes, versions, duplicates, and restart positions. Sending the same API request to both systems leaves a partial-success failure path.
Stopping legacy writes includes batches, admin tools, delayed jobs, and webhooks. A routing flag does not stop work already running. Enforce ownership with permissions or ownership-state checks, and explicitly queue requests or return retryable failures during cutover.
Compare the same scope at a comparable point
Shadow validation compares replacement results without returning them to users. Use a path that cannot send notifications, charge accounts, or update stock. Even reads add load, so limit sampling and frequency.
Two systems each holding 100,000 rows can still disagree: a missing order and an unexpected order cancel in the count. AWS DMS validation compares corresponding rows, reports mismatches, and consumes additional resources. A changed schema also needs business-mapping validation above the row comparison.
| Compare | Conditions | Difference handling |
|---|---|---|
| Order IDs, states, amounts, currencies | Same scope and mapping rules | Separate missing, extra, and changed values |
| Versions and deletion states | Align replication positions or comparison snapshots | Separate in-flight lag, true mismatches, and unverifiable records |
| Shipment, cancellation, and notification outcomes | Same business ID and processing stage | Query external results before deciding to replay |
Reading actively changing data at different times can turn normal replication lag into apparent errors. Define a comparison position and track unvalidated records separately from real mismatches. A lag metric displaying zero does not by itself prove every record has been applied.
Rehearse the full pause-to-resume sequence
Pause new selected writes and drain in-flight legacy updates → Record the final position and apply changes through it → Reconcile business IDs and states; resolve unknowns and mismatches → Fence legacy updates; switch routes and ownership to replacement → Resume replacement writes; verify business outcomes and backlog
Rehearse realistic data volumes and skew, lock waits, replication backlog, interruption, and restart. Measure duration and change the approach if it exceeds the allowed pause. Check the routing layer's capacity, availability, and configuration propagation too.
Expand-contract can stage incompatible schema changes: add a compatible form, migrate consumers, then remove the old form. Keeping columns available does not guarantee value or semantic compatibility. Validate transformations and the versions that run concurrently.
After replacement writes, reversing the route is insufficient
| Rollback point | Required condition | Recovery approach |
|---|---|---|
| Replacement has only served reads | Legacy remains authoritative and current | Return reads to legacy; verify caches and routing state |
| Replacement has accepted writes | Legacy can represent and receive every update and delete | Pause writes, reverse-transform and reconcile, then transfer ownership back |
| New states or external effects cannot be represented in legacy | Routing reversal alone cannot meet safe recovery conditions | Contain impact; recover through replacement fixes or business compensation |
A replacement-only order state can be lost or misinterpreted by legacy. One option is restricting writes to compatible states during the rollback window. If replicating backward, transfer ownership without allowing both systems to become authoritative simultaneously. Database restoration cannot undo a charge or delivered notification; verify external outcomes and handle business compensation separately.
Include legacy retirement in the definition of done
- Use access evidence to verify that reads, writes, batches, and integrations have migrated and no legacy consumers remain.
- Confirm quality and business outcomes over the agreed observation period; resolve pending, mismatched, and unverifiable records.
- Test recovery, backups, permissions, and operational handoff. Assign owners to remove temporary replication and routing.
- Meet required history-retention conditions, then revoke legacy access and credentials and retire unnecessary resources.
Incremental migration needs temporary coexistence mechanisms. Budget their maintenance and define removal conditions during design. Progress to the next unit when business outcomes and ownership transfer are verified, alongside the replacement interface working.
Primary sources for migration patterns and validation
- AWS Prescriptive Guidance — Strangler fig pattern
- Martin Fowler — Parallel Change
- AWS DMS — Data validation
Tips this post digs into
- Start legacy replacement with a switchable boundary and a source of truth
- Capture current behavior before changing poorly understood legacy code
- Ship schema changes as expand, migrate, contract
- Rehearse database migrations with volume, invalid data, and concurrent work
- Separate DB updates and message sends can succeed independently