Specimen your migration Method full-scale impact
The crash-test lab for database migrations.
Watch your migration freeze production — on a crash-test dummy, before your users ever feel it.
No account. No signup. The lab is already running.
This is what a blocking lock does in production.
Three documented cases, three years running. Every one of them was a migration that passed review, passed staging, and then met real traffic.
-
2024
Queries piled up behind it, and clients timed out.
GoCardless Engineering ~15 seconds of unexpected API downtime, during a planned migration
-
2025
Failed to acquire a database lock on a busy table.
GitLab production incident #20494 repeated timeouts, public incident record
-
2026
There were always active queries holding locks.
Autumn postmortem an ALTER TABLE wanting an exclusive lock denied ~10% of API requests
Every one of these was survivable on a dummy first. That is the whole idea.
The loop, in this order, every run.
A crash test is only worth anything if the dummy is full-size, the seats are occupied, and somebody measures the impact. Brace does all three, then throws the car away.
- 01clone10M rows · 894 MB
- 02load24 workers
- 03migratelock_timeout 1.5s
- 04measure250ms samples
- 05verdictunsafe > 1s
-
01
The dummy
A full-size clone of your database — same row counts, same indexes, same planner statistics — created in seconds from a seeded template. Not a 100-row fixture. The lock behaviour you are about to see is the lock behaviour you would get.
-
02
The passengers
Live, labelled write traffic fills the seats: checkouts, logins, cart adds — each with the request timeout every real stack already has. Without traffic, every migration looks safe.
-
03
The crash
Your migration runs against the twin while the traffic is live. If it takes a blocking lock, writes freeze — and the ones that wait past their timeout die, exactly as they would in production. The pulse line goes red as it happens.
-
04
The report
How long writes were frozen. How many writes actually died. Then, clearly labelled as a projection with the formula shown, what that freeze costs at your traffic: denied checkouts, denied logins. Measured numbers first, always.
Linters read the car's blueprint and guess.
Brace crashes the car.
squawk and eugene lint the migration file and warn you by pattern — they never run it.
Postgres.ai gives you a real clone, but it sits idle: no traffic, so no lock contention and nothing to measure.
None of them translate a lock into a number of denied checkouts. That translation is the entire product.
Three services. One import file.
Brace needs a real Postgres superuser, native template cloning, and a private network — so it is built on infrastructure that hands you all three instead of hiding them.
- webstatic dashboard
- apigo engine · sse
- dbpostgres 16 · dedicated vcpu
Every run clones the template into a throwaway twin over the private network, then drops it.
Self-host it with one import file — your production data never leaves your infrastructure.
Rehearse against your schema, not ours.
The stock lab seeds ten million synthetic orders. That is a good demo and a weak rehearsal — your migration meets your indexes, your row widths and your traffic mix, and none of those are things a generated table can stand in for. A self-hosted Brace points at a restored production dump instead. Two environment variables. No fork, no recompile.
-
01
Restore your dump as the template
Every disposable twin is cloned from it.
createdb -U <superuser> brace_template pg_restore -U <superuser> -d brace_template your-dump.dump -
02
Turn seeding off and describe your traffic
Each label is
name:weight:statement— the write mix that runs against the twin while your migration executes.BRACE_SEED: "off" BRACE_LOAD_LABELS: >- checkout:3:INSERT INTO invoices (customer_id, cents) VALUES ($1, $2); login:2:UPDATE sessions SET last_seen = now() WHERE id = $1 % 50 AND $2 > 0 -
03
Run the migration you are nervous about
The casualty count is now measured against your real workload — and every statement is validated once at boot, so a typo is a named startup error, never a rehearsal whose numbers are quietly meaningless.
Brace runs inside your own infrastructure, so the dump never leaves it. The migration under test runs as brace_migrator and the load traffic as brace_traffic — both unprivileged, both owning nothing. Full walkthrough →
Bring your own migration.
Paste the one you are nervous about. It runs on a dummy, not on your users.
Enter the lab