All work
◆ Client work · Under NDA

CRM Data Migration Platform

eSparkbiz2024
Built the database and api layers

The problem

Contacts, tickets, deals, conversations and marketing tags had to move from Freshdesk and SalesManago into HubSpot. Half a million records, three source shapes, one target schema, and no tolerance for silent loss.

Constraints

  • Lambda timed out long before any realistic batch finished
  • Three third-party APIs, each with its own rate limits and failure modes
  • No second attempt: the source systems were being decommissioned
  • Every record had to be provably accounted for, not sampled

Decisions

Decision 01

An SQS queue instead of one long-running function

Rather than one long-running function per batch, SQS orchestrated the whole migration. Each message was a small, idempotent unit that could fail and be retried without touching anything already done. The Lambda timeout stopped being a limit on how much could be migrated at once.

Trade-off

More moving parts and more infrastructure to reason about, in exchange for a job that could survive being interrupted at any point.

Decision 02

MongoDB Atlas as a staging layer

Nothing went straight from source to target. Every record landed in Atlas first, was validated against the target schema, then pushed onward. That gave a queryable record of what had moved, what had failed, and why.

Trade-off

An extra write per record and a second datastore to operate, worth it for being able to answer "did record X make it?" without calling three APIs.

Decision 03

Failures logged and retried, never thrown away

Rate limits, malformed source data and transient API errors were expected rather than exceptional. Each failure was recorded with its cause and re-queued under a backoff, so the run degraded in throughput instead of stopping.

Outcome

  • All 500,000+ records migrated with 100% record-level accuracy on validation
  • Replaced a manual, spreadsheet-driven process that had no audit trail
  • The staging layer meant every record could be traced end to end after the fact

What I'd change

I would build the reconciliation reporting before the migration logic next time. I built it last, and it was the first thing people asked about.