What breaks when you leave Salesforce
Migration cost is about references, not rows. Contacts export cleanly. What breaks is the graph, and you can measure how much will break in an afternoon, before you quote.
The first question a team asks about leaving a CRM is how many records they have, and the number they give is the least useful number in the project. The CRM I built at CustomGlide is a migration target for teams leaving Salesforce, HubSpot and Zoho, so I have spent a fair amount of time on the receiving end of exports. Row counts predict almost nothing. Contacts export cleanly, in every system, every time. What breaks is the graph: the references from one record to another that only mean something inside the source. The migration's cost is the cost of those references, and it can be measured on a trial export before anyone quotes anything.
Rows export, references break
A CRM export is a set of tables that point at each other. A deal points at an account and an owner. An activity points at a contact and at the deal it belonged to. A contact points at the campaign that created it, the user who created it, and a dozen picklist values that were valid on the day it was saved. Every one of those pointers is an identifier that the source system minted and the target system has never seen.
Some resolve. The account the deal points at is in the accounts table, so the pointer can be rewritten to the new id. Many do not. The owner left the company two years ago and was deactivated, so the user record is missing from the export or present without a login. The deal the activity belonged to was deleted, so the activity points at nothing. The picklist value "Proposal sent (old)" was retired in a cleanup and exists only as a string in fifteen thousand records. The campaign id belongs to a marketing tool that was integrated, then replaced, and its records were never in the CRM at all.
None of this shows up in a row count. A hundred thousand contacts with clean references migrate in a day. Ten thousand deals whose owners, stages and activities all point into the past can take a month of decisions, because every unresolved reference is a question: drop it, remap it, or invent a placeholder that someone will have to explain later.
The orphan ratio
The measurement I run before quoting is what I call the orphan ratio. Take a trial export of everything the team wants to keep, load it into a scratch copy of the target model, attempt to resolve every reference, and count the records that have at least one reference which fails to resolve: an owner, an account, a parent, a campaign, a created-by, a picklist value. Divide by the total number of records. That fraction is the orphan ratio, and it predicts effort far better than volume does.
Every orphan then gets one of three treatments, and the proposal should say which classes get which. Reassign, when a live substitute exists: a departed owner becomes a named successor or a house user, and the record keeps its history. Archive with a tombstone, when the parent is gone but the child still carries information: the activity that pointed at a deleted deal keeps a note saying so, in a field the team can filter on, rather than being silently attached to nothing. Invent a placeholder only as a last resort, and only when the team has agreed to explain it later, because placeholders are the migration's debt and every one of them is a question someone will ask in a report six months on.
It takes an afternoon, and most of the afternoon is the export, not the counting. The resolution step is a handful of joins. The output is a table by object type and by reference field, which is more useful than the single ratio, because it says where the orphans are. Owners who left are usually the largest group and the easiest to fix, with one rule: reassign to a named successor or to a house account. Deleted parents are the next largest and the hardest, because the record still carries information and the thing it was about is gone. Retired picklist values are numerous and cheap once someone decides what the old value maps to.
Getting the data out is the easy part
People expect the export itself to be the bottleneck, and it rarely is. HubSpot, for example, raised its API limits in September 2024 to 650,000 requests per day on Professional and 1 million on Enterprise, with a burst limit of 190 requests per 10 seconds for private apps, and its usage guidelines spell out how the two limits interact. Paging at 100 records per request, the burst limit is the binding one, and it works out to under nine minutes per million records.
The chart is there to kill the wrong worry. Nobody's migration is slow because the API is slow. It is slow because of what happens after the records land, and the orphan ratio is the measurement of that.
Where the hours go
Once the export is on disk, the work sorts into five kinds, and the reference work dominates. Resolving references is the bulk: deciding rules for each orphan class, applying them, and checking the result against what the sales team remembers. Field mapping is next: the source has forty custom fields on a contact and the target has a data model, so each field is either mapped, promoted to a proper entity, or dropped with the team's agreement. Picklist reconciliation is cheap per value and expensive in total, because there are hundreds of values and each needs an owner to say what it meant. Validation is the reconciliation of counts and sums before and after, so that the team trusts the result. The transfer itself, the part everyone worried about, is the smallest.
How to quote from the ratio
The orphan ratio turns a guess into a range. A trial export with a ratio under a few percent, concentrated in departed owners, is a migration that will run on rules: write the reassignment rule, apply it, move on. A ratio in the tens of percent, spread across deleted parents and retired values, is a migration that will run on meetings, because each class of orphan needs a decision from someone who remembers what the data meant. The quote should say which kind it is, and it should say so before the price, because the price follows from it.
Two more things the trial export teaches, both worth writing into the proposal. First, which records the team is willing to leave behind. Activities older than a certain date, deals lost before a certain year, and contacts with no activity in years are often the largest source of orphans and the least valuable to migrate, and a team that has seen the ratio by object type will usually agree to an archive rather than a migration for them. Second, what the target model is missing. If a whole class of records has nowhere to land, the trial export finds it on the first afternoon rather than in the final week.
The rule
Never quote a migration from a row count. Export a trial, resolve every reference, count the orphans by object and by field, and quote from that table. The graph is the job. The rows were never the problem.
Get new posts by email
Occasional essays on engineering, AI, and building for the people technology leaves behind.
Subscribe with RSS