Why the window has to go
A retail estate that closes for four hours on a Sunday loses a Sunday. A payments platform that closes for four hours loses trust. The techniques below are not new — shadowing, dual writes, and progressive cutover have been documented for years — but they have moved from premium option to baseline expectation, and that changes the shape of the plan. Most of the effort happens before anything moves.
Rule of thumb: if the cutover hour is the interesting part of your plan, the plan is not finished.
Set up traffic shadowing
Mirror a copy of production traffic to the new stack and compare every response. Nothing is served from the new system yet — you are looking for disagreement. Route 53 weighted records or an ALB rule can fan the copy out; the important part is capturing both responses and diffing them structurally rather than byte for byte.
ShadowTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub '${AppName}-shadow'
Port: 8080
Protocol: HTTP
VpcId: !Ref VpcId
HealthCheckPath: /healthz
ShadowRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
ListenerArn: !Ref HttpsListener
Priority: 10
Actions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref LegacyTargetGroup
Weight: 100
- TargetGroupArn: !Ref ShadowTargetGroup
Weight: 0 # mirrored, never servedDual writes and reconciliation
Once reads agree, start writing to both systems. Run a reconciliation job nightly that compares row counts and checksums per table, and treat any non-zero delta as a blocking defect rather than a known issue. This is the stage teams are tempted to rush; it is also the stage that decides whether the cutover is boring.
# nightly delta check — fails the pipeline on any drift aws dms describe-table-statistics \ --replication-task-arn "$TASK_ARN" \ --query 'TableStatistics[?ValidationFailedRecords>`0`]' \ --output table DELTA=$(aws dms describe-table-statistics \ --replication-task-arn "$TASK_ARN" \ --query 'sum(TableStatistics[].ValidationFailedRecords)') if [ "$DELTA" -ne 0 ]; then echo "Validation drift: $DELTA records — cutover blocked" exit 1 fi
Shift reads progressively
Move read traffic in slices — one percent, then five, then twenty-five — watching p99 latency and your error budget at each step. Hold each slice for at least one full traffic cycle, which for most businesses means a working day rather than an hour. If a slice degrades, shift back and diagnose before proceeding.
Automate the rollback trigger. A human watching a dashboard at 2am is not a control.
Cut writes, then hold
The write cutover is short — usually minutes — because everything is already in place. Afterwards, keep the old estate running and reverse replication armed for a full business cycle, including the month-end run if you have one. Only then decommission. The cost of a fortnight of duplicate infrastructure is trivial against the cost of discovering a month-end batch that only existed on the old system.
# 1. stop writes at the edge
aws elbv2 modify-rule --rule-arn "$MAINT_RULE" \
--actions Type=fixed-response,FixedResponseConfig="{StatusCode=503}"
# 2. drain in-flight, confirm replication lag is zero
while [ "$(replication_lag_seconds)" -gt 0 ]; do sleep 2; done
# 3. promote target, flip weights
aws rds promote-read-replica --db-instance-identifier "$TARGET_DB"
aws elbv2 modify-rule --rule-arn "$SHADOW_RULE" \
--actions Type=forward,TargetGroupArn="$NEW_TG"
# 4. reverse replication stays armed for 14 daysWas this useful?
Tell us what to write next.