A migration that “looks done” and a migration whose numbers match are two different claims. Only one of them survives an audit — and if your site is the business, the audit is coming eventually, usually in the form of a customer asking where their order history went.
Counts are the contract
The discipline is simple to say and unglamorous to do: every entity type, reconciled source-to-destination, every run. Nodes, users, files, terms, comments, orders — counted on the old site, counted on the new one, matched exactly or explained precisely. On the largest migration this practice has run — a monolithic Drupal 7 application with millions of nodes — that rule was the whole ballgame. Zero records lost, and we can say that because we counted, not because it felt right.
Relationships are the hard part
Content is easy to move; the web between content is where migrations quietly fail. Entity references, field collections, term hierarchies, menu positions — the defaults tend to flatten what your editorial logic depends on. Custom migration paths exist for exactly this: preserving the structure the business actually runs on, not just the words.
Rehearse against copies, cut over in minutes
Migrations should run against copies while the live site keeps publishing, with delta runs picking up new content each pass. Done this way, the final cutover is minutes, not a weekend of held breath — the last run has been rehearsed a dozen times before anyone calls it the real one.
If someone quotes you a migration without mentioning counts, ask how they’ll prove nothing was lost. The pause tells you everything. The upgrade assessment writes the counting plan before anyone commits to the trip.