Azure Migration

Migration Wave Planning: Grouping Servers by Dependency, Risk & Business Cutover Windows

Azure Migrate hands you a spreadsheet: 612 servers, each with a readiness verdict, a recommended VM size and a monthly cost estimate. Discovery is done, the assessment is green, the budget is signed. Then someone asks the only question that matters next — “so which ones do we move first, and when?” — and the room goes quiet. That ordering problem is migration wave planning, and it is where most migration programmes quietly fail. Not in the lift, not in the replication, but in the sequencing: an app server moved a sprint before the database it depends on, a “low-risk” file server that turned out to be the auth dependency for nine other systems, a cutover scheduled for the one weekend the finance close runs.

A wave is a batch of workloads you migrate and cut over together as a single, reversible unit of work. Wave planning slices that flat inventory into waves that (1) keep dependent components together so nothing is split across a cutover boundary, (2) cap the blast radius so any failed wave hurts a bounded, recoverable set of systems, (3) sequence from low-risk and low-coupling toward business-critical and tightly-coupled, and (4) land each cutover inside a window the business can absorb. Get this right and migrations become boring — a cadence of small, rehearsed, rollback-ready moves. Get it wrong and you get the migration nobody forgets: the Saturday-night cutover that spilled into Tuesday because three “independent” systems shared a SQL instance nobody mapped.

This article is the planning playbook a senior architect uses to build that wave plan and defend it to a steering committee. You will learn the mental models (dependency, coupling, blast radius, the rollback gate), the grouping rules (what must travel together versus what merely should), how to turn Azure Migrate’s dependency data into concrete move groups, how to tier waves by risk so the program de-risks itself early, and how to pick cutover windows against real business calendars and RTO/RPO targets. The output is a wave table you can put in front of leadership — order, rationale, window and rollback plan per wave — backed by az CLI and Azure Resource Graph queries you can run against your own subscription today.

What problem this solves

The pain arrives at the same moment every time: discovery and assessment are complete, and the program must commit to a sequence. A flat inventory is necessary but useless for execution — it tells you what exists and whether it fits, never in what order to move it. Without a wave plan you get one of two failure modes. The first is paralysis: the team stares at 600 rows, can’t agree where to start, and the migration stalls for a quarter. The second is worse — a confident but wrong order, where someone picks “the easy ones first” by gut, moves an application tier ahead of its database, and discovers mid-cutover that the two were never independent.

What breaks without disciplined wave planning is the cutover itself. Split a dependency across a wave boundary — app in wave 3, its database in wave 5 — and during the gap that app reaches across the network from on-prem to Azure (or back), adding latency, opening firewall holes, and creating a fragile hybrid state someone has to operate. Underestimate the blast radius — pack 40 interdependent systems into one mega-wave to “save weekends” — and a single failure forces you to roll back all 40, turning a contained incident into a program-level crisis. Misjudge the window — cut over a payroll system the night before pay-run — and a 30-minute hiccup becomes a business event with the CFO on the call.

Who hits this: every datacentre-exit and lift-and-shift program beyond a handful of servers. It bites hardest on estates with shared infrastructure (one SQL instance serving many apps, a central Active Directory, a shared file server), undocumented dependencies, and rigid business calendars. The fix is not a tool — Azure Migrate gives you the data — it is a method for converting that data into a defensible order.

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should have already completed discovery and assessment. Wave planning consumes their output: a server inventory with readiness verdicts, sizing and a dependency map. If you haven’t run that step, start with Azure Migrate discovery, assessment and dependency mapping and the Azure Migrate appliance setup that feeds it. You should also know, at a decision level, which tool moves which workload — covered in Azure Migrate vs Site Recovery vs Database Migration Service.

This sits in the plan phase of the Cloud Adoption Framework’s migration motion, after assess and before migrate. It assumes you understand recovery objectives — RTO (how long you can be down) and RPO (how much data you can lose) — from Business continuity and disaster recovery: RTO/RPO fundamentals, because those numbers drive both the cutover method and the window. Familiarity with az JSON output and basic KQL helps, since you’ll query the assessment data programmatically.

Here is the map of who owns which input to the plan, so you pull the right people into planning rather than discovering the gap during cutover:

Input to wave plan Where it comes from Who owns it What a gap here causes
Server inventory + sizing Azure Migrate assessment Cloud/platform team Wrong VM SKU, surprise cost at cutover
Dependency map Azure Migrate dependency analysis Cloud team + app owners Split dependency, broken app post-move
Business criticality tier App owners / business Business stakeholders Critical app in an early risky wave
RTO / RPO per workload Business + BCDR policy Business + BCDR owner Wrong cutover method, missed SLA
Change/freeze calendar Business / change board Change manager Cutover collides with a freeze or peak
Network/firewall topology Network team Network team Hybrid traffic blocked mid-migration
Rollback capability Migration tooling design Migration lead No safe abort, forced to push through

Core concepts

Five mental models make every wave-planning decision obvious. Hold these and the ordering stops being a debate and becomes arithmetic.

A wave is a reversible unit of cutover, not a unit of copying. You can replicate hundreds of servers in parallel for weeks — replication is cheap, continuous and low-risk because the source keeps running. The wave is defined by the cutover: the moment you switch users and traffic from on-prem to Azure and (for most methods) stop the source. Everything in a wave cuts over in one coordinated window and shares one rollback decision. So sizing a wave means sizing the cutover — how many systems you will switch, and possibly revert, in a single night.

Dependency is directional; coupling is about how tightly the cutover binds. A dependency is “A calls B” — an app server depends on its database, a web tier depends on an auth service. Coupling is how damaging it is to separate them across a cutover boundary: a synchronous, chatty, latency-sensitive dependency (app↔database on the same LAN) is tightly coupled and must move together; an asynchronous, occasional, latency-tolerant dependency (a nightly batch that drops a file) is loosely coupled and can span a boundary safely. Wave planning is mostly the work of finding the tight couplings in the dependency graph and refusing to cut across them.

Blast radius is what one failed wave can take down. Every wave you cut over is a bet. The blast radius is the set of systems and users affected if that bet loses and you must roll back. A small wave (a handful of loosely-coupled systems) has a small blast radius — a contained, recoverable incident. A mega-wave has a large one — a failure becomes a program crisis. Capping blast radius is the single most important risk lever in the whole plan, and it directly trades against calendar: smaller waves mean more cutover weekends.

The program should de-risk itself over time, not save the hard part for last. Order waves so the first ones are low-stakes and teach you the runbook — a throwaway pilot, then independent low-criticality systems — and the last ones are the business-critical, tightly-coupled crown jewels you attempt only once the team has cut over a dozen waves flawlessly. You build trust on safe waves, then spend it on the dangerous ones. A program that front-loads its riskiest cutover is one bad night from cancellation.

Every cutover has a gate and a clock. A cutover is not “flip it and hope.” It is: rehearse → freeze → cut over → validate against a checklist → hit a go/no-go gate → commit or roll back. The clock is the window — the agreed downtime the business grants — and inside it sits the point of no return, after which rollback costs more than fixing forward. Wave planning fixes the window (from RTO and the calendar) and the rollback method (from the migration tool) before the night, so the go/no-go decision is mechanical, not heroic.

The vocabulary in one table

Pin down every moving part before the deep sections. The glossary repeats these for lookup; this is the mental model side by side:

Term One-line definition Why it shapes the wave plan
Wave A batch migrated and cut over as one reversible unit The atom of the plan; sized by cutover risk
Move group The set of components that must migrate together A wave is built from whole move groups, never half of one
Dependency “A calls B” — a directional reliance Defines which components are related
Coupling How damaging it is to split a dependency at cutover Tight = same wave; loose = may span waves
Blast radius Systems/users affected if a wave fails and reverts The cap that limits wave size
Cutover The switch from source to Azure (often stops source) The risky moment a wave is built around
Cutover window Agreed downtime the business grants Bounds what can be moved in one go
RTO / RPO Max downtime / max data loss tolerated Drives method and window per workload
Go/no-go gate The checkpoint to commit or roll back Makes abort a decision, not a panic
Point of no return After this, rollback costs more than fixing forward Defines when the gate closes
Freeze A change-frozen period (close, peak, audit) A window you may not cut over in

Core concept 1 — From dependency map to move groups

The dependency map is the raw material; move groups are the first refined product. A move group is the smallest set of components you can move as a unit without breaking a tight coupling. Building them correctly is the foundation — every later decision (wave size, order, window) operates on whole move groups, never on individual servers torn out of one.

Hard grouping rules (“must” travel together)

Some couplings are non-negotiable. If you split them across a cutover boundary, the system is broken or dangerously degraded during the gap. These define the minimum a move group must contain:

Coupling type Example Why it must stay together What splitting it causes
App ↔ its primary database Order app ↔ its SQL instance Synchronous, chatty, latency-sensitive Cross-WAN latency, timeouts, broken transactions
Clustered nodes SQL AG / WSFC cluster members Shared quorum, single logical service Split-brain or failed cluster across sites
Tightly-coupled tiers Web ↔ app ↔ cache on one LAN Sub-millisecond intra-tier calls Latency cascade, user-visible slowness
Shared backing store Several apps on ONE SQL instance One server, cannot half-move it Either move all the apps or none
Stateful session affinity App + its dedicated session store In-flight session state Dropped sessions, forced re-login
License/host-locked pair App bound to a specific license server Hard runtime dependency App fails to start or license-checks fail

The “shared backing store” row is the one that ambushes programs: a single SQL Server instance hosting databases for eight applications cannot be split. You move all eight apps in the same wave as that instance, or none — there is no “move app 3 now, the rest later” when they share one server. This is why discovery must capture which database server each app uses, not just that it uses one.

Soft grouping rules (“should” travel together)

Other couplings are preferences, not laws. Keeping them together is cleaner, but splitting them is survivable for the duration of a migration if you accept a temporary cost (extra latency, a temporary firewall rule, a hybrid link). These let you split a large move group when calendar pressure demands it:

Coupling type Example Why grouping is preferred Cost of splitting (temporarily)
Async / batch integration Nightly ETL reads from app DB Cleaner, no hybrid path A temporary cross-site link for the batch
Read-replica / reporting BI reads a replica of prod Co-location, lower latency Replica lags / reads over WAN for a while
Shared file drop Apps exchange files on a share One location Mount the share cross-site temporarily
Loosely-coupled microservices Service calls another via queue Locality Queue traffic traverses the WAN
Same business domain All “HR” apps together Operational tidiness None technical — purely organizational
Shared monitoring/logging Apps ship to one log sink Single pane Logs route cross-site briefly

The discipline is to write down, for every edge in your dependency graph, whether it is hard or soft. Hard edges fuse components into a single move group; soft edges become the seams you may cut when a move group is too big for one window. A move group with no internal seam is indivisible; one with soft seams can be broken into smaller waves under controlled, temporary hybrid conditions.

Reading dependency data programmatically

Azure Migrate’s dependency analysis produces connection data (which server talks to which, on which port, how often). You don’t have to read it row by row in the portal — once your migration project’s resources land in a subscription you can query inventory and tags with Azure Resource Graph, and you can pull the assessment/dependency exports via the CLI. Confirm what’s discovered:

# List Azure Migrate projects in the subscription
az resource list \
  --resource-type "Microsoft.Migrate/migrateProjects" \
  --query "[].{name:name, rg:resourceGroup, location:location}" -o table

# List the assessment projects (assessments + dependency data live here)
az resource list \
  --resource-type "Microsoft.Migrate/assessmentProjects" \
  --query "[].{name:name, rg:resourceGroup}" -o table

Dependency connection data is exported from the dependency-analysis view (a CSV of source → destination → port → process). The workflow: export that CSV, collapse it to a graph of server→server edges, label each edge hard/soft using the tables above, and find connected components — each connected set of hard edges is a candidate move group. For workloads already in Azure (or for tagging the plan), Resource Graph is your inventory engine:

// Inventory of VMs with their wave tag, to track plan coverage
Resources
| where type == "microsoft.compute/virtualmachines"
| project name, rg = resourceGroup,
          wave = tostring(tags["migrationWave"]),
          moveGroup = tostring(tags["moveGroup"]),
          criticality = tostring(tags["criticality"])
| order by wave asc, moveGroup asc

The move group is the unit you tag and track. Tagging the plan onto resources makes it queryable and auditable rather than trapped in a spreadsheet — pairing with a broader resource tagging strategy.

Core concept 2 — Risk tiering and the de-risking order

Once you have move groups, you order them. The principle is de-risk early: arrange waves so the program proves its runbook on safe workloads first and tackles the crown jewels last, when the team is rehearsed and trust is high. Two axes drive a move group’s position: business criticality (how much it hurts if it’s down) and coupling/complexity (how hard it is to move cleanly).

The risk-tier model

Score every move group on both axes, then place it into a tier. Tiers map directly to when in the program a move group should go:

Tier Criticality Coupling / complexity When in the program Example workload
0 — Pilot Throwaway / non-prod Trivial, self-contained First — to prove the runbook A dev box, an internal wiki
1 — Low risk Low Independent, few deps Early Static marketing site, a standalone tool
2 — Moderate Medium Some deps, one DB Mid Departmental app + its dedicated DB
3 — High High Multi-tier, shared infra Late Customer portal + shared SQL + cache
4 — Critical Business-critical Tightly coupled, regulated Last Core ERP / payments / EHR

The placement logic is deliberately conservative. You never put a Tier-4 workload in the first month, no matter how confident the team is, because the first cutovers will surface runbook gaps — a missing firewall rule, a DNS TTL nobody lowered, a validation step nobody wrote down — and you want those on a dev wiki, not on payments. Equally, don’t waste the whole early program on trivial waves; once the runbook is proven (usually after Tier 0 and one or two Tier-1 waves), accelerate.

Scoring and ordering in practice

Make the tiering explicit. A weighted score per move group turns gut feel into a defensible number:

Factor Weight Score 1 (low risk) Score 5 (high risk)
Business criticality ×3 Internal/non-prod Revenue/safety-critical
Number of dependencies ×2 0–1 Many, cross-domain
Shared infrastructure ×2 Dedicated stack Shared DB/AD/storage
Data sensitivity / regulation ×2 Public data PII / PCI / regulated
Change-freeze constraints ×1 Anytime Tight, rare windows
Stateful / migration difficulty ×1 Stateless Stateful, clustered

Higher total = later wave. The exact weights matter less than applying them consistently, so the order reflects a uniform rubric, not whoever argued loudest. Tag the resulting tier onto resources so the program reports progress by tier, and an auditor sees critical systems were scheduled late deliberately, not by accident.

Wave order at a glance

The de-risking order produces a predictable shape. Most programs converge on something like this, with wave count scaling to estate size:

Wave band Contents Goal Typical posture
Wave 0 Pilot / throwaway Prove and time the runbook Move freely; expect to redo
Waves 1–2 Tier-1 independent systems Build cadence and confidence Standard rollback, low stakes
Waves 3–N Tier-2/3 departmental + portals Bulk of the estate Rehearsed, blast-radius capped
Final waves Tier-4 critical, shared core The crown jewels Maximum rehearsal, board-aware
Decommission Source teardown after soak Realize savings, exit DC Only after soak period passes

This is the table you actually present to a steering committee — concrete, with the order, contents, rationale, window and rollback for each wave. A worked extract for an estate like NorthBridge’s:

| Wave | Contents (move groups) | Tier | Servers | Cutover window | Rollback | Why this position | | — | — | — | — | — | — | | 0 | Internal wiki | 0 | 2 | Any night | Re-point DNS | Throwaway — prove and time the runbook | | 1 | HR self-service + DB | 1 | 6 | Off-peak weeknight | Replication revert | Independent, low criticality, build cadence | | 2 | Facilities tool + DB | 1 | 4 | Off-peak weeknight | Replication revert | Independent, dedicated stack | | 3–5 | Departmental apps | 2 | ≤20 each | Weekend window | Replication revert | Bulk, rehearsed, blast-cap honoured | | 6 | E-commerce + POS + partner API (shared SQL) | 3 | 18 | Sat night, pre-freeze | 2-hr rehearsed revert | Tight coupling + shared DB; one critical wave | | 7 | Core ERP | 4 | 14 | Sat night, post-close | 2-hr rehearsed revert | Crown jewel — last, fully rehearsed |

Core concept 3 — Blast radius and wave sizing

How big should a wave be? Governed by blast radius, not convenience. A wave’s size is the size of the bet, and the bet’s downside is everything that must roll back together if the cutover fails. You cap that downside and size waves to stay under it.

The fundamental trade-off

There is an unavoidable tension between safety and calendar:

Wave-sizing approach Blast radius Cutover weekends needed Rollback complexity When it fits
Many small waves Small (contained) Many Simple (few systems) Critical estates, low risk appetite
Few large waves Large (program risk) Few Hard (many systems at once) Tight deadline, mature team, low coupling
Balanced (typical) Bounded by a cap Moderate Manageable Most real programs

Smaller waves are safer but consume more cutover weekends and stretch the timeline (and the hybrid-state cost). Larger waves finish faster but risk turning one bad cutover into a multi-system rollback. The art is picking a blast-radius cap you can state to leadership, and letting it size the waves.

Choosing a blast-radius cap

Express the cap in terms the business understands and the runbook can honour. Common formulations:

Cap dimension Example limit Why this dimension How to enforce
Max business-critical systems per wave ≤ 1 Tier-4 system One crown jewel at a time Tiering + scheduling rule
Max servers per wave ≤ 20 VMs Bounds rollback effort Wave-sizing rule
Max users impacted per wave ≤ 500 users Bounds business exposure Map systems → user counts
Max move groups per wave ≤ 4 Bounds coordination load Build waves from move groups
Max rollback time per wave ≤ 2 hours Must fit inside the window Rehearse the rollback, time it
One business domain per wave HR only this wave Contains organizational impact Group by domain where possible

The most defensible cap is a combination: “no more than one Tier-4 system, no more than ~20 servers, and a rollback rehearsed to complete inside two hours.” Any candidate wave that violates the cap is split along a soft seam (from the move-group analysis) into two smaller waves. Any trivially small wave can be merged with an adjacent low-risk wave to save a weekend — provided the merge still respects the cap.

Capacity and quota also bound the wave

Blast radius limits the downside; Azure subscription limits bound the upside of how much you can land at once. A wave that cuts over 60 VMs into a region needs the vCPU quota to exist in that region, or the cutover fails on a quota error at the worst possible moment. Pre-flight every wave against quota:

# Check regional vCPU quota for the SKU family your wave lands on
az vm list-usage --location southeastasia \
  --query "[?contains(localName,'Standard Dv5')].{name:localName, used:currentValue, limit:limit}" \
  -o table

If a wave’s target SKUs would exceed the limit, raise the quota before the cutover weekend (quota increases are covered in Azure quota management and vCPU limit increases). The same applies to public IPs, managed-disk counts and network resources — a wave is only as deployable as the quota underneath it.

Core concept 4 — Cutover windows and method

A move group’s position (which wave) comes from risk; a wave’s timing (which night, how long) comes from the cutover window — the intersection of what the business tolerates (RTO and the calendar) and what the migration method requires.

Hard cutover vs near-zero-downtime cutover

The two cutover styles demand very different windows; choosing wrong wastes a weekend or blows an SLA:

Cutover style How it works Downtime Window needed Best for
Hard cutover (cold) Stop source, final copy, start in Azure Minutes to hours A real maintenance window Stateless tiers, tolerant apps
Replication-based (warm) Continuous replication, brief final sync, switch Minutes (RPO ≈ near-zero) Short switch window Servers needing minimal downtime
Online DB migration Continuous DB sync, cut at parity Seconds–minutes Short cutover, long pre-sync Databases with strict RTO
Phased / parallel run New runs alongside old, traffic shifts None (gradual) No downtime window Stateless web, API behind a gateway

For server lift-and-shift, replication-based cutover keeps the source running while data replicates continuously, so the actual downtime is only the final sync + switch — minutes, not the hours a cold copy would take. The tool-selection nuance (server replication versus a database-specific online migration) is the subject of Azure Migrate vs Site Recovery vs Database Migration Service; databases with strict RTO almost always want an online migration via Database Migration Service rather than a file-level copy.

Mapping RTO/RPO to method and window

The recovery objectives are not paperwork — they pick the method. Read this as a decision table:

If the workload’s RTO is… …and RPO is… Then the cutover should be… Implied window
Hours (tolerant) Hours (tolerant) Hard/cold cutover is fine Weekend maintenance window
Low (≤ 1 hr) Hours Replication-based, brief switch Short off-peak window
Low (≤ 1 hr) Near-zero Continuous replication + final sync Minutes, off-peak
Near-zero Near-zero Online DB migration / phased Seconds; gradual traffic shift
Effectively zero Zero Active-active / parallel run No downtime; cut traffic gradually

The key planning move is to attach an RTO and RPO to every move group, because those two numbers, more than anything else, decide whether a wave needs a four-hour weekend window or a fifteen-minute Tuesday-night switch.

Reading the business calendar

The window is not just “a weekend” — it is a weekend the business will grant, which means avoiding every freeze. Map the calendar before scheduling anything:

Calendar constraint Typical period Why it’s a no-cutover zone Planning response
Financial close Month/quarter/year-end Finance systems must be stable Schedule around close dates
Retail / seasonal peak Black Friday, holidays Revenue peak, zero risk appetite Hard freeze; migrate before/after
Regulatory reporting Filing deadlines Compliance-critical stability Freeze the affected systems
Payroll run Pay cycle dates Payroll cannot be late Never cut payroll near a run
Major business events Product launch, AGM Visibility, no surprises Avoid; communicate widely
Existing change freeze Holidays, code freezes Org-wide change ban Respect; no cutovers

The output of this step is a valid-window map per workload: the set of nights this move group may cut over, after subtracting every freeze. A wave’s window must satisfy the intersection of valid windows for every move group in it — another reason move groups in the same wave should share a business domain, since they tend to share a calendar.

The go/no-go checklist

Inside every window sits the gate. Define its checklist before the night so the commit-or-rollback call is mechanical. A reusable per-wave checklist, with the abort condition for each item:

Gate check Confirm with Pass criteria If it fails
App health / smoke test Synthetic transaction in Azure Core user journey works end-to-end Fix-forward if quick; else roll back
Dependencies reachable App logs / connection test No cross-site or failed calls Roll back if a hard edge was split
Data integrity / final sync Row counts / checksum vs source Source and target at parity Roll back — never commit divergent data
Security controls present NSG effective rules / Network Watcher On-prem rules recreated exactly Recreate the rule, re-validate
Performance acceptable Latency / error-rate metrics Within agreed thresholds Investigate SKU/coupling; roll back if severe
Time remaining in window The clock vs point-of-no-return Enough window to fix or revert Past PONR → push forward; else roll back

Architecture at a glance

Read the wave plan as a left-to-right pipeline that turns a flat inventory into ordered, gated cutovers. On the left, Azure Migrate sits in your discovery subscription, having produced two artifacts: the assessment (readiness, SKU, cost per server) and the dependency map (who-talks-to-whom). These feed the planning layer: dependency edges are labelled hard or soft to fuse servers into move groups, each move group is risk-tiered (criticality × coupling) and assigned an RTO/RPO, and a blast-radius cap slices the tiered move groups into ordered waves. The output is a wave table — Wave 0 (pilot) through the final critical wave — annotated with contents, order, window and rollback method, and projected onto resources as tags so the whole plan is queryable in Azure Resource Graph.

On the right is execution. Replication runs continuously and in parallel for every wave’s servers (the source keeps serving), so the cutover window is only the final sync + switch. Each wave flows through a fixed gate: rehearse (a non-disruptive test failover to prove the runbook), freeze the source, cut over inside the granted window, validate against a per-wave checklist, then hit the go/no-go gate. Pass and you commit and begin the soak; fail and you roll back to the still-intact source — which is why replication-based cutover stays reversible up to the point of no return. The numbered badges mark the five most common failures: a missed dependency that splits a move group (1), a wave exceeding the blast-radius cap (2), a cutover colliding with a freeze (3), a validation gap that lets a broken wave pass the gate (4), and a missing or unrehearsed rollback path (5).

Left-to-right migration wave-planning architecture: Azure Migrate assessment and dependency map feed a planning layer that builds move groups, risk-tiers them, applies a blast-radius cap and emits an ordered wave table tagged into Azure Resource Graph; the execution lane runs continuous replication then a rehearse-freeze-cutover-validate-gate flow with rollback, with numbered badges on the five most common wave-planning failures

The shape to internalize: discovery produces facts, the planning layer produces an order, execution turns each wave into a gated, reversible cutover. Nothing crosses from plan to execution until its move group is whole, its tier is assigned, its wave fits the cap, and its window clears every freeze.

Real-world scenario

NorthBridge Retail (fictional, but a composite of real programs) ran a datacentre exit: 480 servers, a hard lease-end deadline 11 months out, and a retail business with an absolute freeze from mid-November through early January. Their first instinct was the classic mistake — “move the easy stuff first” — and an engineer drew up a plan that put the e-commerce web tier in an early wave because it was stateless and looked simple, leaving the order database for a later wave because “databases are scary.”

Dependency mapping killed that plan. Azure Migrate’s connection data showed the web tier making thousands of synchronous calls per minute to the order database on port 1433 — a textbook tight coupling. Splitting them across waves would have meant the Azure-hosted web tier reaching back over the WAN to an on-prem SQL Server for every page load: latency spikes, timeout storms, a fragile hybrid path open for two months. Worse, the graph revealed that three applications — the e-commerce site, the in-store POS sync service, and a partner API — shared a single SQL instance. That instance was an indivisible move group: all three had to move in the same wave as that one database server, or none could.

The replan tiered honestly. Wave 0 was a throwaway internal wiki, used purely to time the runbook (three attempts to get DNS TTLs and firewall rules right — exactly the gap you want to find on a wiki, not on checkout). Waves 1–2 were independent departmental apps with dedicated databases, used to build cadence. The shared-SQL trio became Wave 6: three customer-facing systems, one database, ~18 servers, capped and rehearsed with a two-hour rollback. Its window was a Saturday night in late October, before the November freeze, with the explicit fallback that if it slipped the whole trio waited until mid-January — attempting it during peak was off the table.

The cutover used continuous replication: the source kept serving customers right up to a brief Saturday-night final sync, so real downtime was 40 minutes, not the six hours a cold copy of that database would have taken. At the go/no-go gate, the validation checklist caught a real problem — the partner API was returning 500s because a firewall rule for the partner’s source IP hadn’t been recreated in the Azure NSG. Because the source was still intact, the call was easy: they fixed the NSG rule inside the window, re-validated, and committed. A cold cutover with the source already wiped would have forced a rollback or an extended outage into Sunday. The lease-end deadline was met with six weeks to spare, and the program’s repeated retro lesson was: the dependency map, not the gut feel, decides the order.

Advantages and disadvantages

Disciplined wave planning is not free — it front-loads analysis and stretches the timeline. Weigh it honestly:

Advantages Disadvantages
Contained blast radius — a bad cutover hurts a bounded set More cutover weekends than a “big bang”
Dependencies never split — no fragile hybrid state Significant up-front planning effort
Program de-risks early — runbook proven on safe waves Longer overall calendar (hybrid state persists)
Each wave is rehearsed and rollback-ready Coordination overhead per wave (comms, freezes)
Defensible to leadership — order has a stated rationale Tempting to over-engineer the plan
Cutovers land in windows the business can absorb Dependency data must be accurate or the plan is wrong
Plan is queryable/auditable via tags Plan drifts if not maintained as the estate changes

The disadvantages cluster around time and effort: wave planning trades a faster-looking “big bang” for a slower, safer cadence, and it is only as good as the dependency data feeding it. The advantages cluster around risk: every one of them is about making failure survivable. The decision is essentially risk appetite. For a small, loosely-coupled estate with a mature team and a hard deadline, a few large waves can be defensible. For anything critical, regulated, or tightly-coupled, the small-wave discipline pays for itself the first time a cutover goes wrong and the blast radius is 12 servers instead of 200.

The break-even case is the mid-size estate. Below ~50 servers, the planning overhead can outweigh the benefit — a handful of waves with obvious ordering. Above a few hundred servers with shared infrastructure, skipping wave planning is how programs end up cancelled. The honest middle path is the balanced sizing from the blast-radius table: a real cap, waves built from whole move groups, and a de-risking order — enough rigor to be safe, not so much that the plan becomes the project.

Hands-on lab

This lab builds a miniature wave plan end to end using Azure Resource Graph and tags — no migration tooling required, free-tier friendly. You will tag a small set of resources with a wave plan, then query the plan back out exactly as you would on a real program. (If you have no spare VMs, create two tiny B1s or substitute any existing resources; the mechanics are identical.)

1. Set variables and confirm your context.

RG="rg-wave-lab"
LOC="southeastasia"
az group create --name "$RG" --location "$LOC" -o table

2. Create three throwaway resources to stand in for three move groups. (Use the smallest SKU; delete at the end.)

# Three tiny resources to represent three workloads in different move groups
az vm create -g "$RG" -n vm-wiki    --image Ubuntu2204 --size Standard_B1s \
  --admin-username azureuser --generate-ssh-keys --no-wait
az vm create -g "$RG" -n vm-hrapp   --image Ubuntu2204 --size Standard_B1s \
  --admin-username azureuser --generate-ssh-keys --no-wait
az vm create -g "$RG" -n vm-hrdb    --image Ubuntu2204 --size Standard_B1s \
  --admin-username azureuser --generate-ssh-keys --no-wait

3. Tag the wave plan onto the resources. vm-wiki is the pilot (Wave 0); the HR app and its database share a move group and a wave (Wave 2), because they are tightly coupled:

az resource tag --tags migrationWave=0 moveGroup=pilot     criticality=low \
  -g "$RG" -n vm-wiki  --resource-type "Microsoft.Compute/virtualMachines"
az resource tag --tags migrationWave=2 moveGroup=hr-stack  criticality=medium \
  -g "$RG" -n vm-hrapp --resource-type "Microsoft.Compute/virtualMachines"
az resource tag --tags migrationWave=2 moveGroup=hr-stack  criticality=medium \
  -g "$RG" -n vm-hrdb  --resource-type "Microsoft.Compute/virtualMachines"

4. Query the wave plan back with Azure Resource Graph (the same query you’d run on a real estate to verify coverage):

az graph query -q "Resources
| where type == 'microsoft.compute/virtualmachines'
| where resourceGroup == 'rg-wave-lab'
| project name, wave = tostring(tags['migrationWave']),
          moveGroup = tostring(tags['moveGroup']),
          criticality = tostring(tags['criticality'])
| order by wave asc" -o table

Expected output groups vm-hrapp and vm-hrdb under wave 2 / move group hr-stack, and vm-wiki under wave 0 — your move-group integrity is now queryable.

5. Find plan gaps — any VM missing a wave tag is unplanned (the check that catches forgotten servers):

az graph query -q "Resources
| where type == 'microsoft.compute/virtualmachines'
| where resourceGroup == 'rg-wave-lab'
| where isempty(tostring(tags['migrationWave']))
| project name, status = 'UNPLANNED — assign a wave'" -o table

6. (Optional) Express the plan as IaC so it is version-controlled, not click-ops. A minimal Bicep that stamps wave tags onto a VM:

param location string = resourceGroup().location
param vmName string
param wave string
param moveGroup string
param criticality string

resource vm 'Microsoft.Compute/virtualMachines@2024-03-01' existing = {
  name: vmName
}

// Apply the wave-plan tags as a tags resource (merges onto the VM)
resource waveTags 'Microsoft.Resources/tags@2024-03-01' = {
  name: 'default'
  scope: vm
  properties: {
    tags: {
      migrationWave: wave
      moveGroup: moveGroup
      criticality: criticality
    }
  }
}

7. Teardown — delete everything so the lab costs nothing:

az group delete --name "$RG" --yes --no-wait

You now have the core loop of plan governance: tag the plan, query coverage, find unplanned servers, and (optionally) codify it. On a real program this same loop runs against hundreds of servers and becomes the live dashboard the steering committee reads.

Common mistakes & troubleshooting

Wave planning fails in predictable ways. Each row below is a real failure mode: the symptom you’d notice, the root cause, how to confirm it, and the fix.

# Symptom Root cause How to confirm Fix
1 App broken/slow right after its wave Its database was in a different wave (split dependency) Dependency map shows a hard edge crossing a wave boundary Re-fuse into one move group; move them together
2 One failed cutover forces a huge rollback Wave too large — blast-radius cap ignored Count systems/users in the wave vs the cap Split the wave along a soft seam
3 “Independent” file server took down 9 systems Hidden dependency not discovered Re-run dependency analysis; check connection data Extend discovery window; map then re-tier
4 Cutover collided with finance close Business calendar not consulted Compare cutover date to freeze calendar Reschedule outside the freeze
5 Can’t move app 3 without apps 1,2,4 They share one SQL instance (indivisible group) Assessment shows one DB server, many app deps Move all apps on that instance in one wave
6 Cutover failed on a quota error mid-window Regional vCPU/IP quota too low for the wave az vm list-usage shows used near limit Raise quota before the cutover weekend
7 Broken wave passed the go/no-go gate Validation checklist too shallow Review checklist vs what actually broke Add real end-to-end checks; re-test
8 No safe way to abort a bad cutover No rehearsed rollback / cold cutover wiped source Check the cutover method’s reversibility Use replication-based cutover; rehearse rollback
9 Hybrid traffic blocked after partial move Firewall/NSG rule for cross-site path missing Network Watcher / NSG effective rules Pre-create the temporary hybrid rules
10 Plan drifted; servers cut over untracked Tags/plan not maintained as estate changed Resource Graph shows untagged or stale waves Re-baseline tags; gate changes on the plan
11 Riskiest system cut over in week one Order ignored de-risking (criticality not tiered) Tier of the first waves vs business criticality Re-order: pilot and Tier-1 first, Tier-4 last
12 Cutover ran long, spilled past the window Rollback/cutover never timed in rehearsal Compare rehearsal timing to the window length Rehearse to a stopwatch; size window to fit

The three most damaging are #1 (split dependency), #5 (shared backing store), and #8 (no rollback). All three are planning failures, not execution failures — baked in before the cutover night and discovered the hard way during it. The discipline that prevents all three is the same: trust the dependency map over intuition, build waves from whole move groups, and never schedule a cutover whose rollback you have not rehearsed and timed.

A short confirm-and-fix decision table for the live-incident moment, when a wave is mid-cutover and something is wrong:

If during cutover you see… It’s probably… Do this
A dependent app erroring post-switch A dependency you left on-prem Check for cross-site calls; if a hard edge was split, roll back
Validation check failing, source intact A config gap (NSG/DNS/firewall) Fix forward inside the window; re-validate; only roll back if it can’t be fixed in time
Quota / capacity error on deploy Regional limit hit Abort this wave; raise quota; reschedule
Cutover running past the window Underestimated time Hit the go/no-go gate; if past point-of-no-return, push forward; else roll back
Performance far worse in Azure Wrong SKU or a split chatty dependency Validate sizing; if a tight coupling was split, that’s the cause

Best practices

Security notes

Migration is a window of elevated risk: data is in motion, temporary access is granted, and hybrid paths are open. Bake security into the wave plan, not after it.

Cost & sizing

Wave planning has two cost dimensions: the temporary cost of running the migration and a hybrid state, and the steady-state cost the assessment already estimated. Plan for both.

Cost driver When it applies Rough magnitude How to control it
Double-running (source + target) During each wave’s soak period ~2× that workload’s cost, briefly Keep soak periods short and bounded
Replication storage/transfer While replicating before cutover Per-GB cache storage + egress Don’t replicate everything months early
Hybrid connectivity (VPN/ExpressRoute) While any wave is mid-migration Hourly gateway + egress Size the gateway to the migration, not forever
Cutover-weekend effort Each wave’s window Team hours (the real cost) Fewer, well-rehearsed waves
Quota/SKU surprises At cutover if under-planned Failed cutover (worst cost) Pre-flight quota per wave
Decommission savings (negative) After soak, on source teardown The whole point — DC exit savings Don’t delay decommission; realize it

The biggest hidden cost is prolonged hybrid state: every extra week a wave sits half-migrated, you pay for the source, the target, the replication storage and the connectivity gateway simultaneously — and you operate a split system. This is the real argument against waves that are too small (more, longer-lived hybrid states) and against starting replication months before cutover. Replicate close to the cutover, soak briefly, then decommission.

For the steady-state target cost, lean on the assessment’s sizing and then optimize: right-size against the recommendation, and apply reservations or savings plans to the predictable post-migration baseline (see Azure reservations vs savings plans). A cost-sequencing rule: migrate the cheapest-to-run-twice workloads early while the runbook is unproven, and save the expensive double-run workloads for late waves you cut over fast and decommission quickly.

Rough INR/USD intuition for the temporary overhead on a mid-size estate: a single VPN/ExpressRoute gateway for the migration window runs on the order of a few thousand rupees per day, replication cache storage scales with the data churned, and the double-run cost is just the target VMs’ run rate for the soak weeks. None of these are large against the DC-exit savings — but only if the hybrid window stays short. A migration that drags its hybrid state for a year quietly erases its own business case.

Interview & exam questions

1. What is a migration wave, and what defines its boundaries? A wave is a batch of workloads migrated and cut over together as a single reversible unit. Its boundaries are defined by four constraints: dependency integrity (no tight coupling split across the boundary), a blast-radius cap (bounded failure/rollback scope), risk ordering (de-risk early), and a cutover window the business can absorb. Maps to AZ-305 (designing migrations) and the CAF migrate methodology.

2. Why must an application and its primary database usually be in the same wave? Because the app↔database dependency is typically synchronous, chatty and latency-sensitive — a tight coupling. Splitting them across waves forces the migrated tier to call the other over the WAN, causing latency, timeouts and a fragile hybrid path for the duration of the gap. They form one indivisible move group.

3. Three applications share a single SQL Server instance. How does that constrain your waves? That instance is an indivisible move group: you cannot half-move a single server. All three applications must migrate in the same wave as that database server, or none of them can. This is why discovery must capture which database server each app uses, not merely that it uses one.

4. Explain blast radius and how it sizes a wave. Blast radius is the set of systems and users affected if a wave’s cutover fails and must roll back. You set a cap on it (e.g. one critical system, ~20 servers, two-hour rehearsed rollback) and size waves to stay under the cap. It is the primary risk lever and trades directly against the number of cutover weekends.

5. What is the “de-risk early” ordering principle and why does it matter? Order waves so the earliest are low-stakes (a pilot, then independent low-criticality systems) and the latest are business-critical and tightly-coupled. The first cutovers always surface runbook gaps; you want those surfaced on a dev wiki, not on payments. The team builds trust on safe waves and spends it on the crown jewels last.

6. How do RTO and RPO influence the cutover method? Tolerant RTO/RPO (hours) allows a cold/hard cutover in a maintenance window. Low RTO with near-zero RPO demands continuous replication with a brief final sync. Near-zero RTO/RPO needs an online database migration or a phased parallel run with gradual traffic shift. The objectives, not preference, pick the method.

7. Why prefer a replication-based cutover over a cold copy for risky waves? Because the source keeps running until the final sync, the cutover is reversible right up to the go/no-go gate — if validation fails, you roll back to the intact source with minutes of downtime. A cold copy that stops and wipes the source removes that safety net, turning a fixable config gap into a forced outage.

8. What is the go/no-go gate, and what decision does it govern? It is a checkpoint after cutover-and-validation where you decide to commit or roll back. With the validation checklist and the point-of-no-return defined in advance, the decision is mechanical: checklist green → commit and soak; checklist red and unfixable inside the window → roll back. It removes heroics from the abort decision.

9. How do business freeze calendars enter wave planning? You subtract every freeze (financial close, retail/seasonal peak, payroll runs, regulatory deadlines, change freezes) from the candidate cutover windows, leaving a valid-window map per workload. A wave’s window must satisfy the intersection of valid windows for every move group in it — a reason to group by business domain, which tends to share a calendar.

10. What’s the difference between a hard and a soft grouping rule? A hard rule is a coupling that cannot be split without breaking or dangerously degrading the system (app↔DB, cluster nodes, shared backing store) — it fuses components into one move group. A soft rule is a preference (async batch, reporting replica, shared file drop) that can be split temporarily at the cost of a hybrid path — these are the seams you may cut to fit a window.

11. How would you make a wave plan auditable and queryable rather than a static spreadsheet? Project the plan onto resources as tags (migrationWave, moveGroup, criticality, classification) and query coverage with Azure Resource Graph — including a query for untagged/unplanned servers. Ideally codify the tagging in IaC so it’s version-controlled. This turns the plan into a live dashboard and an audit trail.

12. A “low-risk independent” file server, once migrated, broke nine other systems. What went wrong and how do you prevent it? A hidden dependency was never discovered — the file server was actually a shared dependency for nine systems. Prevention: run dependency analysis over a long enough window to catch periodic/low-frequency calls, treat the connection data as authoritative over assumptions, and re-tier when discovery reveals new edges.

Quick check

  1. You discover an app makes thousands of synchronous calls per minute to a database on the same LAN. Hard or soft coupling, and what does that imply for waves?
  2. Your blast-radius cap is “≤ 20 servers, one critical system, two-hour rollback.” A candidate wave has 35 servers across two business domains. What do you do?
  3. A workload has an RTO of “a few hours” and an RPO of “a few hours.” Which cutover method and what window?
  4. Why should the pilot wave be a throwaway, non-production workload?
  5. During a cutover, a validation check fails but the source is still intact and replication is running. The gap is a missing NSG rule fixable in 15 minutes, and you have 90 minutes of window left. Commit or roll back?

Answers

  1. Tight (hard) coupling — synchronous, chatty, latency-sensitive. Implication: the app and database must be in the same move group and the same wave; never split them across a cutover boundary or the migrated tier will call the other over the WAN with latency and timeout problems.
  2. Split it along a soft seam. The wave violates the cap (35 > 20, and spanning two domains). Find a soft (loosely-coupled) seam from the dependency analysis and divide it into two compliant waves — ideally one per business domain so each shares a calendar.
  3. A hard/cold cutover in a weekend maintenance window is sufficient. With tolerant RTO and RPO you don’t need continuous replication or an online DB migration; stop the source, do the final copy, start in Azure inside the granted window.
  4. Because the first cutovers always surface runbook gaps — DNS TTLs, firewall rules, validation steps nobody wrote down. You want those gaps discovered on a system whose failure doesn’t matter, so the runbook is proven before any real workload cuts over.
  5. Fix forward, then commit. The source is intact (reversible), the gap is a known config fix, and it fits comfortably inside the remaining window. Recreate the NSG rule, re-validate the checklist, and commit. You only roll back if the gap can’t be fixed before the window closes or you pass the point of no return.

Glossary

Next steps

AzureMigrationAzure MigrateWave PlanningDependency MappingCutoverCloud Adoption FrameworkASR
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading