Rehearse first: how I run risky changes on production Odoo databases
Some changes can't be undone with a git revert. Merging two companies, cleaning transactional data before go-live, or upgrading a module that rewrites HR or payroll records all change data. If the change goes wrong, the only way back is a backup you trust.
Over the last year I've settled on one runbook for this kind of work. It's deliberately boring.
1. Read-only analysis first
Before writing a single migration, I query the database read-only. I want to know:
- how many records each step will touch
- which sequences, stock valuations and accounting balances depend on them
- which scheduled jobs or outgoing integrations could fire while I'm working
The output is a short written plan with numbered phases.
2. A backup you have actually restored
A backup is three things, and all three are checksummed:
pg_dump -Fc "$DB" -f "${DB}_${LABEL}_${TS}.dump"
tar czf "${DB}_${LABEL}_${TS}_filestore.tgz" -C "$FILESTORE" .
sha256sum *"${TS}"* > "${DB}_${TS}.sha256"
I don't trust the backup until I have restored it. I verify the checksum, run pg_restore -j4 into a new database, and compare row counts table by table against the live database.
3. Rehearse on a neutralised copy
The restored copy becomes the rehearsal environment. Before touching it I:
- mark it
IS_TEMPLATE, so the application can't list or open it by accident - disable scheduled jobs and outgoing mail
- disable any e-invoicing or payment provider
- regenerate the database identifiers, so the copy can never talk to external services as if it were production
Then I run the change exactly as it will run in production. I compare business fingerprints before and after: stock valuation, account balances, record counts per company.
If the rehearsal fails, I fix the script and rehearse again. One of my cleanups needed five rehearsal runs before the numbers matched. That's normal, and it's far better than finding out in production.
4. An approval gate the script enforces
The production script refuses to run unless:
- it is pointed at the expected database (it checks
current_database()) - an explicit
--approvedflag is passed - no application processes and no other database sessions are connected
So a mistake in the terminal fails safely instead of corrupting data.
5. Execute, verify, keep the rollback ready
The main phase runs inside a single transaction. Afterwards I run read-only SQL checks and an ORM-level verification script. I compare the results with the rehearsal, and I keep the evidence (logs, fingerprints, valuation exports) next to the backup.
The rollback plan is written before execution, not after. It has three levels:
- Undo the phase.
- Restore the database from the dump.
- Restore the database and the filestore together.
What this buys you
On the projects where I've used this runbook, the production run was the least interesting part of the day: it did exactly what the rehearsal did. That's the goal.
If you work with ERP databases, I'd love to hear how you handle this. Get in touch on LinkedIn.