A legacy SaaS platform was being retired and the business was moving to a new vendor system. I owned the migration — mapping ~20 business objects and 300+ fields source-to-target on Google Cloud, reconciling every record before cutover, and running the vendor and change management — so the switch landed with zero disruption to live operations.
Scope & strategy
The migration, end to end
One picture of the whole thing: the legacy platform's data flows through a BigQuery engine that maps, transforms, matches, and reconciles it — and nothing crosses into the new platform until parity passes.
The legacy system stays live and read-only in parallel (the amber path) — so any wave can be rolled back cleanly until the new platform has proven itself.
What kind of migration — and why
This wasn't lifting infrastructure to the cloud. The incumbent SaaS platform was being sunset by the vendor, and the business had chosen a replacement product. The data had to move from one vendor's schema to a completely different one — while the business kept operating on it. Three things made it hard:
Two schemas that disagree
Different field names, types, code values, and grain. Nothing mapped one-to-one — every field needed a rule.
Live cycles couldn't stop
Transactions, approvals, and payouts were in flight. A hard switch would freeze the business, so downtime had to be effectively zero.
Integrity is non-negotiable
The platform fed CRM, ERP, warehouse, and BI. One mismapped field would silently corrupt everything downstream.
Choosing the strategy
Before touching data, I classified the move against the industry-standard 7 Rs of migration. Naming the strategy up front is what turns a data project into a governed migration — it sets the plan, the risks, and the exit criteria.
| Strategy | When you use it | Fit here |
|---|---|---|
| Rehost | Lift-and-shift infrastructure as-is to new hosting. | No — there's no server to move; the platform is vendor SaaS. |
| Replatform | Move with light optimization in flight. | No — not a re-hosting exercise. |
| Refactor | Re-architect an application you own. | No — we don't own the app; it's a vendor product. |
| Repurchase | Retire the tool and adopt a new SaaS product (drop-and-shop). | Yes — the business moved to a new vendor platform. |
| Retire | Decommission what's redundant or end-of-life. | Yes — the legacy platform and duplicate objects were retired at cutover. |
| Retain | Keep on the current platform for now. | No — vendor end-of-life forced the move. |
| Relocate | Move VM workloads (e.g. VMware) to cloud. | No — not a VM workload. |
The call — Repurchase + Retire
The incumbent was being sunset and a replacement product was already chosen, so Repurchase was the only honest classification — paired with Retire for the legacy system and its redundant data. That framing set everything downstream: the work was a data migration into a purchased product, so success hinged on the source-to-target mapping and reconciliation, not on re-architecting anything.
Strategy locked: Repurchase + Retire, executed phased with a parallel run.
How to cut over
Strategy answers what; execution answers how you flip the switch. With live cycles running, the execution model was the real risk decision.
| Approach | What it is | Decision |
|---|---|---|
| Big bang | Move everything in one cutover window. | Rejected — live cycles would freeze, and one failure means a total rollback. |
| Phased | Migrate in object-group waves, each with its own QA and reconciliation. | Chosen — contains blast radius; each wave proven before the next. |
| Parallel run | Old and new run together; legacy kept read-only through hypercare. | Chosen — zero downtime, and a clean backout if parity fails. |
Every object-group wave ran the same governed loop — and only a parity pass earned a go-live.
The business could not pause. A phased wave plan let me reconcile each object group in isolation, and a parallel run kept the legacy system available (read-only) as the rollback path until the new platform had proven itself. Slower than a big bang — but the only responsible choice when payments and approvals are live.
The governance spine
The migration ran on a standard assess → design → implement → validate lifecycle with hard gates. That is what makes a migration auditable instead of hopeful.
Discovery & inventory
Every field, from the legacy schema to the new one, with a transform rule and a validation gate. This single artifact reconciled three things at once — the old schema, the new schema, and what the data means to the business — and it was the contract the vendor built against.
A migration fails in the gaps between "what the old system stored" and "what the new system expects." Writing every field down — with a rule and a check — is what turns those gaps from production incidents into review-desk decisions.
| Source (legacy) | Target (new) | Transform / rule | Validation gate |
|---|---|---|---|
| opportunity_id | deal_id | Direct copy | Not null, unique (primary key) |
| account_name | account_id | Golden-record match to CRM master; similarity ≥ 0.92 | 100% resolved or exception queue |
| amount | amount_usd | Convert to USD via fx_rate on close_date | Σ(source) = Σ(target) within ±0.5% |
| status | stage_code | Value-map lookup (enum) | No orphan or unknown codes |
| created_ts (UTC) | created_date | Cast UTC timestamp to reporting-timezone date | Date-shift / off-by-one audit |
| region_string | region_id + subregion_id | Split delimited string + reference lookup | Referential integrity, no nulls |
Field and table names are representative of the method, not any employer's schema. The real map ran to 300+ fields across ~20 objects — the discipline of a rule and a gate on every field is the point.
The build — hands-on GCP
The parity check below is the go/no-go — it compares source and target by object group, and a clean run returns zero rows.
-- Row-count + control-total parity by stage; a clean run returns 0 rows WITH src AS ( SELECT status AS stage, COUNT(*) rows, SUM(amount_usd) amt FROM `legacy_platform.opportunity` WHERE _ingest_date = @cutover_date GROUP BY status ), tgt AS ( SELECT stage_code AS stage, COUNT(*) rows, SUM(amount_usd) amt FROM `new_platform.deal` WHERE load_date = @cutover_date GROUP BY stage_code ) SELECT COALESCE(s.stage, t.stage) AS stage, s.rows, t.rows, s.rows - t.rows AS row_delta, ROUND(SAFE_DIVIDE(ABS(s.amt - t.amt), s.amt)*100, 2) AS amt_pct_delta FROM src s FULL OUTER JOIN tgt t USING (stage) WHERE s.rows != t.rows OR ABS(s.amt - t.amt) > 1;
The same suite also ran, per wave:
Every check had to pass to clear Gate 3.
Contingency & rollback
Rollback isn't a panic move — it's a decision made calmly, in advance, against numbers agreed before go-live.
Triggers set up front
Rollback thresholds were defined before cutover — measurable conditions (parity failures, blocked cycles) that flip a wave from go to no-go, decided cold rather than mid-incident.
A live backout path
The parallel run kept the legacy system read-only through hypercare, so reverting a wave was a known, rehearsed operation — not a scramble.
Mock cutover + exceptions
A dry run tested timing and coordination; unresolved records went to an exception queue with an owner, never into the target unvalidated.
Vendor & change management
A vendor migration is as much a people problem as a data one: an incoming vendor building to spec, an outgoing platform being retired, and a business that has to keep trusting its numbers throughout.
The field map was the contract the incoming vendor built against. In mapping review I found a source field with no clean target that would have silently corrupted downstream data — I escalated it, held the go-live gate until the vendor closed it, and only then reconciled and cut over. The vendor built to one agreed spec instead of guessing.
| Dimension | How I ran it |
|---|---|
| Waves | Sequenced into object-group waves — each mapped, reconciled, and signed off before the next — so risk stayed contained and progress stayed visible. |
| Stakeholders | Kept business, data/IT, finance, and the vendor implementation team aligned to one plan and one field map — a single source of truth for what was moving and when. |
| Comms & adoption | Told affected teams what changed, when, and what to check — so the new platform's numbers were trusted from day one, not second-guessed. |
| Trust | Made reconciliation visible: the same control totals reconciled on both systems, so no one had to take the migration on faith. |
Outcome
The measure of a good migration is that the business barely noticed it happened — the data was simply there, correct, in the new system.
Zero disruption
Phased waves and a parallel run meant live operating cycles kept running throughout — no freeze, no downtime window imposed on the business.
Reconciled at cutover
Every object group passed source-to-target parity before it went live; nothing crossed on faith, and there was a rehearsed backout if it hadn't.
A clean retirement
The legacy platform was retired on schedule with its data fully accounted for — the point of a Repurchase + Retire done right.
The honest version of the impact
I won't attach an invented percentage to this. What's true and defensible: a legacy vendor platform was retired and its ~20 objects / ~60 tables / 300+ fields landed in the replacement product — mapped field-by-field, reconciled before every cutover, and delivered with no disruption to live operations. The strategy was named, the risk was gated, and the vendor built to a spec I held them to.
Competencies demonstrated