Article

Rehearse first: how I run risky changes on production Odoo databases

2026-11-03 · Ahmed Muhammed

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 --approved flag 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:

  1. Undo the phase.
  2. Restore the database from the dump.
  3. 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.

← All articles