DEV Community

Zain Amjed
Zain Amjed

Posted on

Wave planning when nobody can tell you what talks to what

The first wave should be small and boring. That is easy to agree with and hard to do, because the customer wants the important thing moved first, and the important thing is usually the one with the most dependencies nobody has written down.

Every large migration I have worked on has started from the same position: there is no CMDB, or there is one and it is three years stale, which is worse because people trust it. The dependency map you need does not exist and will not exist in time. Wave planning is the discipline of building a schedule anyway, and then being honest about which parts of it are arithmetic and which are guesses.

This is how I build one.

Sprint zero is not wave one

Number your sprints from zero.

Sprint 0 is discovery, landing zone build, and the security posture work that has to exist before a single server replicates. It produces no migrated workloads. If you number it Sprint 1, the first status report shows a sprint completed with nothing migrated, and you spend the next meeting explaining why that is fine instead of talking about the plan.

Numbering from zero makes the shape of the programme legible to whoever is reading the plan without attending the standups. The platform gets built, then waves start.

The work that belongs in sprint zero, in the order it has to happen:

  1. Discovery and planning
  2. Security and compliance requirements
  3. Design discussion and finalisation with the customer
  4. Landing zone security posture update

That fourth item is the one that slips. The landing zone gets built early, then the security review lands late and changes the guardrails, and every server already replicated into the account inherits a configuration that no longer matches the standard. Do the hardening before the first wave replicates, not in parallel with it.

Work backwards from three constraints

The planned cutover date is the obvious constraint. Everyone has it. The two people forget:

Available bandwidth. Replication is not instant. Initial sync for a wave is measured in days, and the total storage in a wave — plus how many waves you are replicating concurrently — decides whether your dates are real. This is arithmetic. You can compute it, and you should, before agreeing to anything.

Application owner availability. Somebody has to validate the application after cutover, and that person has a job. They are not assigned to your migration. They have a release of their own, or annual leave, or they are the only person who understands two systems and you have scheduled both in the same window.

Of the three, owner availability is the one that most often sets the real date. Bandwidth is arithmetic you can plan around; a person with a day job is not. That is the constraint I now solve for first, because it is the least elastic.

Put public holidays in the plan as a column

This sounds like project-management trivia. It is not.

In every version of a wave plan I have built, public holidays sit in the sprint calendar as a first-class column, next to the sprint dates. Not as a note. Not as something checked at the end.

The reason is specific. A cutover weekend that collides with a public holiday does not just lose you a working day. It loses the application owner who is supposed to validate, and the customer-side approver who signs off, and often the third-party vendor support you were relying on if something does not come up. All three disappear at once, and you find out on the Friday.

Putting holidays in the calendar column means the collision is visible while dates are still being negotiated, which is the only time it is cheap to fix.

If the estate spans countries, this compounds — the holiday that matters is the one where the approver sits, not where the servers are.

Group by application, not by server

A wave is a set of applications, not a set of servers.

MGN supports this directly. You associate source servers with an Application, and applications with a Wave, and the reason that hierarchy exists is that servers are not independently migratable. A wave built from servers that happen to be adjacent in a spreadsheet delivers nothing you can hand back to a user. A wave built from applications delivers something that works.

This is also why discovery output matters more than an inventory. An inventory tells you what exists. You need to know what breaks when a given box moves — and in the absence of a dependency map, that knowledge lives with the application owners, which is another reason their availability binds so hard.

Where I have no dependency data at all, I group by what the owners tell me, then validate with a test cutover rather than trusting the grouping. The test cutover is the dependency map.

The rule that saves an application owner

Never put two sets of applications with the same owner in the same cutover window.

It sounds like scheduling trivia and it is the difference between an owner who validates carefully and one who is being asked to be in two places at 3am, signs off on both, and finds the problem on Monday.

Environment and criticality drive the same decision. Production servers need a defined cutover window, a rollback window, and an owner present. Test and UAT servers do not, which is what makes them good first-wave candidates — they let the team learn the process without a rollback plan being load-bearing.

Plan DR as a parallel track, not a later phase

This is the structural point most plans get wrong, and it is expensive to correct.

Every application in a serious plan appears twice: once as a production workstream, and once as a matching DR workstream. Not "migrate everything, then build DR." The DR track is sized, sequenced, and cut over alongside production, wave by wave.

The reason is that DR design decisions are made during the migration, whether or not you acknowledge them. Which AMIs you take, whether the recovery site is pre-provisioned, what the identity path looks like from the DR network — those get decided when you build the wave. If DR is a later phase, you re-open every wave you have already closed, and you make those decisions again with less context and a customer who considers the project finished.

Practically, the DR track for a wave looks like: take AMIs of the production servers, launch servers in the DR site from those AMIs, and test the launch. That work sits in the same sprint as the production cutover for those applications.

It also changes your capacity arithmetic. A wave is not N production servers. It is N production plus M DR servers, and the replication bandwidth you need covers both. Sizing a wave on the production count alone is how a schedule that looked achievable stops being achievable in month three.

Complete the test cutover two weeks before the real one

Two weeks is not padding.

It is the time to find a problem, fix it at source, and let replication re-establish before the real cutover. Compress it to two days and a test failure becomes a cutover slip, because you no longer have room to re-sync.

MGN supports this directly: launch test instances into a prepared subnet, verify, then mark the servers ready for cutover. The test launch is also where you discover the boot-order assumption nobody documented, the licensing that is bound to a MAC address, and the agent that was installed but never actually replicating.

Fix at source, not in the target. A fix applied only to the launched test instance is a fix you will make again on cutover night, under time pressure, from memory.

Checkpoints, not a status call

Establish checkpoints that monitor replication across a wave, rather than asking for status.

Replication drifts quietly. A server that stalled a week ago is discovered on the night nobody was watching lag. The failure mode is silent and the dashboard is green at the wave level while one server inside it has stopped.

Weekly is enough while a wave is replicating. Daily in the fortnight before a cutover, and checking lag and lifecycle state per server rather than reading a wave summary — because a wave reads healthy while one server inside it has stalled.

Wire the stalled-state event to an alert rather than relying on someone opening the console. The console will not tell you at 2am.

UAT is the step that repeats

In the plans I have built, customer UAT is the most frequent task in the entire schedule. More frequent than cutover itself.

That is not a scheduling accident. Every wave carries a customer-validation step, and it is the step most likely to run twice — because validation finds something, it gets fixed, and validation runs again. Cutover happens once per wave if it goes well. UAT happens as many times as it needs to.

Plan for it as a repeating step with owner time attached, not as a checkbox at the end of the wave. A plan that budgets one UAT pass per wave is a plan that slips the first time someone finds a defect.

Documentation and project signoff get the same treatment: scheduled tasks with their own rows and their own capacity, not something assumed to happen after the technical work finishes.

The spreadsheet

Every migration I have seen runs on one, whatever tooling sits underneath.

At minimum it carries: the server, its application, its environment, its owner, its wave, and its current replication state. When there is no CMDB, this document becomes the CMDB, and it is usually the most accurate record the customer has ever had of their own estate.

Add the columns that reflect the constraints above — the sprint calendar with public holidays in it, and the paired production and DR counts per wave. The plan should make the two things that actually bind, bandwidth arithmetic and owner availability, visible on the same screen.

For a large estate

Above roughly a dozen servers per wave, configuring launch templates by hand in the console stops being reasonable. It is an afternoon of clicking with no audit trail, and no way to prove afterwards what a given server was configured with.

Three documented ways out: set an account-level default launch template, script the copying of settings between servers, or import target configuration from a CSV built during discovery.

The third is the one that closes the loop, because discovery data becomes launch configuration rather than being re-typed. I build that CSV in the same spreadsheet the wave plan lives in, which means the plan and the configuration cannot drift apart.

For the scripted paths I use Ansible and shell, driven from the same inventory. The point is not the tool. It is that the configuration is derived from a record you can inspect afterwards, rather than from what someone remembered to click.

Cadence

Two-week sprints track a wave through test and cutover inside a single sprint. Four-week sprints batch more waves per sprint and report less often.

Both work. The trade-off is reporting frequency against ceremony overhead, and it is worth deciding deliberately rather than inheriting whatever the customer's delivery team already runs. On a programme with fourteen sprints, the difference compounds into a materially different number of checkpoints.

What actually decides it

Bandwidth and dependencies decide what can be in a wave. Owner availability decides what should be.

Most plans I have seen fail on the second.

The first wave should be small and boring because it is where the team learns the process — the handover between whoever runs replication and whoever validates the application, the escalation path at 3am, how long the customer's approval actually takes as opposed to how long it is supposed to take. Learn that on something that does not matter.

Then the second wave is where you find out whether the plan is real.

If you have run a large migration, I would be interested in which constraint set your schedule.

Top comments (0)