The Migration Checklist for When You Cannot Afford a Maintenance Window

The Migration Checklist for When You Cannot Afford a Maintenance Window

For a lean technical team running cloud-native infrastructure on AWS, GCP, or bare-metal, a migration without a maintenance window is not a stunt. It is a constraint. The business cannot stop taking orders, the API cannot go dark for four hours, and the database cannot be frozen while a lift-and-shift completes. This article defines a no-window migration as a stateful or stateless move that must preserve write availability, read consistency, and rollback capability while the old and new environments overlap. Adjacent concepts include blue-green deployment, canary release, dual-write, logical replication, and traffic shadowing. The reason this matters to a team of two to fifteen engineers is simple: when there is no dedicated SRE coverage, the migration itself becomes the incident you are most likely to cause.

Two engineers reviewing a migration runbook on a monitor in a server room

This checklist is written for teams that already practice repeatable backup and recovery drills and keep access hygiene tight. If you have not yet written a recovery checklist, start there before attempting a no-window migration. The sequence below assumes you can restore from a tested backup, rotate credentials without breaking a pipeline, and read a monitoring dashboard without guessing.

1. Define the Migration Boundary Before Touching Infrastructure

The first failure pattern in no-window migrations is scope drift. A team starts by moving a PostgreSQL database and ends up also changing the web server image, the Terraform module layout, and the DNS provider. Each extra change multiplies the rollback surface. Write the boundary as a single sentence: “We are moving the customer-order database from AWS RDS PostgreSQL 14 in us-east-1 to GCP Cloud SQL PostgreSQL 15 in us-central1, with no schema changes and no application code changes.” If the sentence needs an “and,” split the migration into two separate events.

Name the systems that are out of scope. For example, object storage, queue workers, and cron jobs may stay on the old provider for weeks. This is not a failure; it is a deliberate reduction of blast radius. The Recovery Checklist Before You Need It applies here: every out-of-scope system still needs a documented rollback path, because a failed migration can take down adjacent services through shared credentials or network paths.

2. Inventory State, Not Just Services

A no-window migration fails when the team discovers state that was never in the runbook. The inventory must include:

  • Databases: primary, replicas, read-only endpoints, and any manual snapshots used by analytics.
  • Object storage: buckets, lifecycle policies, cross-region replication, and signed URL consumers.
  • Message queues: backlog depth, dead-letter queues, and consumer group offsets.
  • Secrets and certificates: expiration dates, rotation owners, and hard-coded references in CI/CD.
  • DNS and load balancers: TTLs, health check intervals, and any IP allowlists.

For each item, record the current provider, the target provider, and the cutover method. A database may use logical replication, while a queue may use a consumer-side dual-read. Do not assume one method fits all state types.

3. Choose a Cutover Pattern That Matches the Data

There are three patterns that work without a maintenance window. Each has a specific failure mode.

3.1 Dual-Write with Backfill

The application writes to both the old and new datastores. A backfill job copies historical data. This pattern works for PostgreSQL, MySQL, and most document stores. The failure mode is write skew: if one write succeeds and the other fails, the two systems diverge. Mitigate this with a reconciliation job that compares row counts and checksums every five minutes during the overlap period. Tools like PostgreSQL logical replication can reduce the application-level dual-write burden for databases, but they do not cover caches or search indexes.

3.2 Read-Only Replica Promotion

Create a replica in the target environment, let it catch up, then promote it. This works for databases that support streaming replication, such as PostgreSQL and MySQL. The failure mode is replication lag. If the replica is 30 seconds behind when you promote it, you lose 30 seconds of writes. Monitor lag with a metric like pg_stat_replication.replay_lag and set an alert at 5 seconds. Do not promote until lag has been under 5 seconds for at least 15 minutes.

3.3 Traffic Shadowing with Gradual Cutover

Send a copy of read traffic to the new environment while writes continue on the old. This validates the new stack under real load without affecting users. The failure mode is false confidence: shadow traffic does not exercise write paths, transaction isolation, or failure recovery. Use shadowing only as a pre-cutover validation step, never as the sole migration strategy.

Network traffic dashboard showing shadowed read requests during a migration

4. Build the Rollback Path Before the Forward Path

A no-window migration is a bet that you can reverse course in under five minutes. That means the rollback path must be tested before the forward migration starts. For a database move, the rollback is usually a reverse replication stream from the new primary back to the old primary. For a stateless service, rollback is a DNS or load balancer flip. Write the exact commands in the runbook, including the verification step: “After rollback, run SELECT count(*) FROM orders WHERE created_at > cutover_time on the old primary and confirm it matches the new primary.”

Do not rely on the forward migration tool’s built-in rollback. Most tools can undo their own changes, but they cannot undo the state changes made by the application during the overlap period. Your rollback plan must account for data written to the new system after cutover.

5. Set Monitoring Thresholds That Trigger a Halt

Before the migration, define the numbers that mean “stop.” These are not the same as your normal alerting thresholds. A migration-specific halt threshold is tighter and tied to a specific action. For example:

  • Replication lag: halt if lag exceeds 10 seconds for more than 2 minutes.
  • Error rate: halt if 5xx responses exceed 0.5% of requests for 5 minutes.
  • Write latency: halt if p99 write latency on the new system exceeds 200 ms for 10 minutes.
  • Data divergence: halt if the reconciliation job finds more than 10 mismatched rows in any 5-minute window.

Each halt threshold needs a named owner and a pre-written action. “Halt” does not mean “discuss in Slack.” It means execute the rollback runbook. If the team is not willing to roll back automatically at a threshold, the threshold is decoration.

6. Run the Migration as a Series of Small, Reversible Steps

The no-window migration is not a single command. It is a sequence of ten to twenty small steps, each reversible on its own. A sample sequence for a PostgreSQL move from AWS RDS to GCP Cloud SQL looks like this:

  1. Create the target Cloud SQL instance with the same PostgreSQL major version.
  2. Configure network peering or VPN between the two environments.
  3. Start logical replication from RDS to Cloud SQL.
  4. Verify replication lag is under 5 seconds for 15 minutes.
  5. Deploy the application with a read-only connection string pointing to Cloud SQL.
  6. Run a shadow read test against Cloud SQL for 30 minutes.
  7. Enable dual-write in the application, with reconciliation enabled.
  8. Run reconciliation for 24 hours and confirm zero mismatches.
  9. Flip the primary write connection to Cloud SQL.
  10. Keep the RDS instance as a rollback target for 7 days.
  11. Decommission RDS after the rollback window closes.

Each step has a verification command and a rollback command. If step 7 fails, you stop dual-write and continue on RDS. If step 9 fails, you flip the write connection back to RDS and investigate. The sequence is boring on purpose. Boring is what a no-window migration should feel like.

7. Test the Migration on a Copy, Not a Hope

Before the real migration, run the entire sequence against a staging copy of production data. This is not a sandbox with fake data; it is a restored snapshot of the production database with the same schema, indexes, and row counts. The staging run will expose problems that documentation cannot: a missing extension, a hard-coded IP address, a connection pool setting that defaults to the wrong value. Time the staging run. If the staging cutover takes 45 minutes, the production cutover will take at least 45 minutes, plus the time you spend reading the runbook under pressure.

If you cannot afford a full staging environment, use a subset of production data that preserves the same table relationships. A 10% sample of orders with all related customer and payment rows is better than a full copy of orders with no related rows. The goal is to exercise foreign keys, indexes, and query plans, not to fill disk space.

8. Communicate the Migration Without Announcing a Window

“No maintenance window” does not mean “no communication.” It means the communication is about risk, not downtime. Tell stakeholders what is changing, what they should watch for, and what to do if they see an anomaly. A short message to the support team is enough: “Between 14:00 and 18:00 UTC, we are moving the order database to a new provider. Users should see no change. If you see a spike in order failures, escalate to the on-call engineer immediately.”

Do not promise zero downtime. Promise a rollback plan. The difference matters when something goes wrong. A team that promised zero downtime will hide a 30-second error spike. A team that promised a rollback plan will report the spike, roll back, and learn from it.

9. Run a Post-Migration Review That Feeds the Next Checklist

After the migration, hold a one-hour review with the people who ran the commands. The review is not a blame session. It is a checklist update. Ask three questions:

  1. Which step took longer than the runbook estimated?
  2. Which verification command did we skip or rush?
  3. What did we learn that should change the next migration checklist?

Write the answers into the runbook. If the reconciliation job missed a table, add that table to the inventory. If the DNS TTL was too long, lower it before the next migration. The goal is a checklist that gets shorter and sharper with each use, not a document that grows into a novel.

10. Keep the Old Environment Alive Longer Than You Think You Need

The most common post-migration regret is decommissioning the old environment too early. A database that looked healthy for 24 hours can fail on day 5 when a monthly batch job runs for the first time. Keep the old environment in read-only mode for at least one full business cycle: a week for most teams, a month for systems with monthly billing or reporting jobs. The cost of a read-only RDS instance for 30 days is a fraction of the cost of a failed migration with no rollback target.

During the overlap period, run a daily reconciliation job that compares row counts and checksums between old and new. If the job finds a mismatch, you still have the old system to investigate. If the job runs clean for the full overlap period, decommission with confidence.

Engineer checking a reconciliation report on a laptop during a post-migration review

FAQ

What is the difference between a no-window migration and a blue-green deployment?

A blue-green deployment switches traffic between two identical environments in a single step, usually at the load balancer or DNS level. A no-window migration often involves stateful systems like databases and queues, where the cutover is gradual and requires data synchronization before the traffic switch. Blue-green is a pattern within a no-window migration, not a synonym for it.

How do I know if my team is ready for a no-window migration?

You are ready if you can answer yes to three questions: Can you restore from a tested backup in under 30 minutes? Can you roll back a database write connection in under 5 minutes? Can you monitor replication lag and error rates with alerts that page a human? If any answer is no, start with a recovery drill before attempting a no-window migration.

What is the most common cause of a failed no-window migration?

Scope drift. A team starts with a database move and ends up changing the application image, the Terraform provider, and the DNS vendor in the same event. Each extra change adds a new failure mode that the rollback plan does not cover. Keep the migration boundary to one system, one provider change, and one cutover method.

How long should I keep the old environment after a successful migration?

At least one full business cycle. For most teams, that means 7 to 30 days. The old environment should be read-only, with a daily reconciliation job comparing it to the new environment. Decommission only after the reconciliation job has run clean for the entire overlap period.