← All guides

Migrations & tech debt

Platform moves, automation consolidation, and paying down the accretion that makes every change slower: how to migrate the contract, not just the rows.

GTM Systems guide

The worst migration I ever inherited had 282 active automations firing on the Opportunity object. Nobody could tell me what any single one did, because the org had eleven admins over six years and each one added a workflow rule rather than touch the one already there. Saving an opportunity took nine seconds and sometimes threw a CPU-limit error that no rep could parse. That is tech debt in its terminal form: not a bug, but a fog where every change risks breaking something invisible, so nobody changes anything, so the fog thickens. We got it down to 82 automations over a quarter, and the first hard part was not deleting them. It was proving what each one still did.

Migrations and tech debt are the same problem seen at two time scales. A platform move (HubSpot to Salesforce, SBQQ to a new CPQ, a home-grown routing script to LeanData) is tech debt paid down all at once under a deadline. Day-to-day debt is the same accretion paid down never, until it forces a migration. The teams that stay fast treat both as the same discipline: migrate the contract first, the rows second, and never carry logic across you cannot explain.

282 → 82
Automations after one consolidation pass
60-70%
Migration effort that is mapping, not moving
2x+
Cost of migrating rows before the contract

Migrate the contract, not the rows

The instinct on a platform move is to start with the data: export everything, map the columns, load it in. That is backwards. The rows are the easy part. The hard part is the data contract, the set of agreements about what each object means, what grain each field lives at, and who writes it. Land rows against an undefined contract and you recreate the mess you were trying to leave, at a new vendor’s monthly rate.

So the order is: define the target object model, define the field contract (grain, writer, definition), define the matching keys, and only then move data. If half your source accounts store the domain as acme.com and half as a full URL, normalize before the load, because the match key is the spine everything else hangs on.

Migration order of operations The contract is the migration; the rows are the payload
Object modelgrain mapField contractwriter, defMatch keysnormalizedDataAutomationrebuilt
Object model first, then the field contract, then match keys, then data, then automation rebuilt on the new model. Reverse this order and every later step inherits the ambiguity of the one you skipped.

Consolidating automation is archaeology before surgery

The 282-automation org taught me the sequence. You cannot safely delete what you cannot explain, so the first pass is pure archaeology: catalog every automation, its trigger, its actions, and the last time it actually fired. Debug logs and the metadata itself tell you the last part. Anything that has not fired in a year is a candidate, but a candidate is not a verdict, because some logic only fires on rare events (a renewal, a specific stage jump).

Then you group by object and by intent. Ten workflow rules that each stamp one field on Opportunity save become one before-save flow. Before-save flows also run an order of magnitude faster than the old workflow-rule-plus-process-builder stack, which is what killed the nine-second save.

// Consolidation smell test: how many separate automations touch one object?
// High counts here are where CPU limits and unexplainable behavior live.
SELECT TableEnumOrId, COUNT(Id) automations
FROM WorkflowRule
GROUP BY TableEnumOrId
ORDER BY COUNT(Id) DESC
Automation count on the Opportunity object, before and after
Consolidating overlapping workflow rules and process builders into before-save flows cut both the count and the save time. Illustrative of a real 282 to 82 pass.
View as table
StageValue
Before: workflow rules141 automations
Before: process builders88 automations
Before: legacy flows53 automations
After: consolidated flows82 automations

Platform moves: what actually breaks

What teams plan for What actually breaks
Object mapping Contacts to contacts, one to one Source has no Lead object, target does; pre-qualification stage has no home
Automation Rebuild the same rules Half the source rules were dead; nobody knows which half
History Migrate open records Reporting needs closed history too, at a grain the source never stored
Identity Match on email Free-mail and shared inboxes collapse distinct people into one
Cutover One weekend Dual-run period where both systems are writing and drifting
The record counts are never the problem. The undefined edges are. Budget the migration around the right-hand column.
A platform migration that does not recreate the mess
  1. 1

    1. Define the target contract first

    Object model, field grain, one writer per field, match keys. This is 60 to 70% of the real work and none of it is loading rows.

  2. 2

    2. Profile the source before mapping

    Duplicate rate, null rates, free-text sprawl, dead automations. You cannot map what you have not measured.

  3. 3

    3. Normalize match keys at the source

    Strip domains to the bare host, dedup, resolve identity. A broken key poisons every downstream match.

  4. 4

    4. Rebuild automation on the new model, do not port it

    Recreate intent, not implementation. Dead source rules stay dead; live ones become consolidated flows.

  5. 5

    5. Dry-run into a sandbox and reconcile counts

    Load into a full sandbox, reconcile every object count and ARR total against the source before touching production.

  6. 6

    6. Dual-run, then cut over on a reconciled number

    Run both systems briefly, freeze writes on the source, cut over only when the target reconciles to the source exactly.

Paying debt down before it forces your hand

The debt you pay on a schedule is cheaper than the debt that forces a migration. A standing quarterly hygiene pass keeps the fog from thickening: retire unused fields, deactivate automations that stopped firing, collapse overlapping validation rules, and delete reports nobody opens. None of it is glamorous and all of it compounds, in the good direction this time.

A migration is never really about the new platform, and tech debt is never really about the old code. Both are about whether anyone can still explain what the system does. Migrate the contract first, consolidate by intent, deactivate before you delete, and keep the export. Do that and the platform underneath becomes a choice you make on purpose, instead of a hostage situation you inherited.