Founder & Lead Engineer, RAITHub
RAITHub ships and tests production software. See QA as a Service or talk to us.
A fintech data migration has one rule above every other: the money must be identical on the other side. Before you move anything, capture control totals. Keep amounts in integer minor units so nothing is rounded in flight. Make each step idempotent and reversible, run both systems in parallel to compare, and cut over only when the totals match to the cent and reconcile clean.
If you would rather have the migration done safely for you, see how RAITHub would build it at the end of this guide.
What makes a fintech migration riskier than an ordinary one?
That a mistake is money, not a cosmetic bug. A dropped row in a blog table is an inconvenience; a dropped ledger entry is a balance that no longer reconciles, and a doubled one is a customer who appears to be owed money they are not. The general recovery and prevention playbook is in recovering from a broken migration; this post adds the controls that keep money exact.
| Ordinary migration | Fintech migration |
|---|---|
| A missed row is noticed later | A missed entry breaks the ledger balance immediately |
| Rounding rarely matters | A cent of rounding compounds and fails reconciliation |
| Re-running usually just overwrites | Re-running can double-count money if not idempotent |
| Rollback is a convenience | Rollback is mandatory, and must be exact |
| Verification is a spot check | Verification is control totals, to the cent |
How do I prove no money was lost or doubled?
With control totals: a small set of sums you capture on the source before the migration and re-capture on the target after. If they match exactly, the money moved intact. If they do not, you stop and investigate before cutover, not after.
-- Capture on the SOURCE before migrating; re-run on the TARGET after.
-- Every figure must match to the cent, in integer minor units.
SELECT
(SELECT COUNT(*) FROM ledger_entry) AS entry_count,
(SELECT SUM(amount_minor) FROM ledger_entry) AS ledger_net, -- expect 0
(SELECT COUNT(*) FROM payment) AS payment_count,
(SELECT SUM(amount_minor) FROM payment
WHERE status = 'captured') AS captured_total,
(SELECT COUNT(DISTINCT account_id) FROM ledger_entry) AS account_count;
-- Per-account balances must match row for row (no account drifts).
SELECT account_id, SUM(amount_minor) AS balance_minor
FROM ledger_entry
GROUP BY account_id
ORDER BY account_id;
Run the per-account query on both systems and diff the results. A ledger that nets to zero on the source must net to zero on the target, and every account balance must be identical. Store the amounts as integer minor units throughout, never floating point, which is the only way these sums stay exact; the reasoning is in double-entry ledger database design.
How do I make the migration safe to re-run?
Make every step idempotent and reversible, so a failure halfway through is recoverable instead of catastrophic. A fintech migration will be interrupted at some point; plan for it.
- Idempotent writes. Key every migrated record on its source ID with a unique constraint, so a re-run skips what already moved instead of inserting it again.
- Batched and checkpointed. Move data in batches, recording the last batch completed, so you resume rather than restart.
- Reversible. Keep the source read-only and intact until cutover is confirmed; rollback is switching back, not restoring from guesswork.
- No transformation of amounts in flight. Copy integer minor units as-is. If units genuinely differ, convert in one audited function and reconcile the totals before and after that step.
- Freeze writes during cutover. A short maintenance window beats a migration racing live transactions and losing some.
What does a safe cutover sequence look like?
Run the old and new systems side by side, compare, then switch. The parallel step is what turns a leap of faith into a verified move.
- Snapshot and freeze. Snapshot the source, then freeze writes for the window.
- Capture source control totals. The numbers you will have to match exactly.
- Migrate in idempotent batches, checkpointing as you go.
- Capture target control totals and diff per-account balances. Any difference stops the cutover.
- Reconcile once more against the gateway and bank, so you are sure the new system agrees with the outside world, using the reconciliation diagnostic.
- Cut over, point the app at the new database, and keep the frozen source read-only for a defined period as your rollback.
- Watch closely for the first full cycle, with the daily balance check running.
Buy, build or hire?
| Option | Example | Choose this when | Where it stops |
|---|---|---|---|
| Vendor migration tool | A managed database or provider's import tooling | Moving between supported systems with simple data | No money-specific control totals or ledger checks |
| One-off script | A hand-written migration script | Small, one-time move with a maintenance window | Easy to get idempotency and totals wrong |
| Build a migration pipeline | Batched, checkpointed, reconciled migration code | Large ledger, several tables, must be reversible | You own the verification and the cutover plan |
| Hire an engineering team | Plan, migrate, reconcile and cut over | Money is on the line and you cannot risk it | Not a substitute for an accountant verifying the books |
How long does it take to do yourself?
Our estimate, for a developer who knows both schemas: a few days for a small, single-table move with control totals and a maintenance window; two to four weeks for a large ledger with idempotent batching, parallel comparison and a tested rollback. The main risk of doing it alone is a non-idempotent re-run after a mid-migration failure, which double-counts money, or a silent unit conversion that rounds a fraction off every row.
How RAITHub would build this
- Plan: map both schemas, define control totals, and write a cutover and rollback runbook before touching data.
- Migrate safely: idempotent, checkpointed batches keyed on source IDs, with amounts copied as integer minor units.
- Verify: control totals and per-account balances diffed to the cent, then reconciled against the gateway and bank.
- Cut over: a short frozen window, the source kept read-only as rollback, and the daily balance check running after.
Timeline: 2–4 weeks as a code-rescue or backend engagement for a typical ledger migration; a small move is shorter. What you receive: automated tests and CI, a cutover and rollback runbook, handover docs, and full IP under NDA. Production and financial data stay in your own cloud account; development uses synthetic data.
RAITHub is an engineering studio, not an accounting firm, and has not shipped a regulated or licensed fintech product; verifying the migrated books for tax and audit is a question for your accountant, and this is general information, so confirm with your adviser. Proof we can point to: Sundor Skin runs 146 PostgreSQL tables with enforced money and credit rules under 530+ automated tests, and TheSkinProof, the founder's own venture, runs multi-gateway payments with 750+ tests. Next step: a free 15-minute technical audit, then a written fixed quote. Book the audit. More is on the SaaS development page and the FinTech page.
Frequently asked questions
How do I know no money was lost in a migration?
Capture control totals on the source before you migrate (entry counts, the ledger net, captured totals, per-account balances) and re-capture them on the target after. Every figure must match to the cent, and per-account balances must be identical. A difference stops the cutover.
Why keep money in integer minor units during a migration?
Because floating point rounds, and a fraction of a cent per row compounds into a reconciliation failure. Copy integer minor units as-is and never transform amounts in flight unless you convert in one audited function and reconcile around it.
How do I make a migration safe to re-run?
Key every migrated record on its source ID with a unique constraint so a re-run skips what already moved, batch and checkpoint so you resume rather than restart, and keep the source read-only until cutover is confirmed so rollback is a switch, not a restore.
Should I migrate live or in a maintenance window?
Freeze writes for a short window at cutover. A migration racing live transactions can lose some of them, and a fintech system cannot afford lost or half-written money data. A short window is far safer than a live race.
What is the safest cutover sequence?
Snapshot and freeze, capture source totals, migrate in idempotent batches, capture and diff target totals, reconcile against the gateway and bank, then cut over while keeping the frozen source as a rollback for a defined period and watching the first full cycle closely.
Related posts
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.