BRACEcrash-test lab for database migrations
how it works docs enter the lab →

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.

writes / sec  ·  live specimen nominal
01
without brace · shipped blind 15.75s freeze · 312 writes died in production
02
with brace · caught on a dummy 0.00s blocked · 0 died · same index
incidents on record

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.

  1. 2024
    Queries piled up behind it, and clients timed out.

    GoCardless Engineering ~15 seconds of unexpected API downtime, during a planned migration

  2. 2025
    Failed to acquire a database lock on a busy table.

    GitLab production incident #20494 repeated timeouts, public incident record

  3. 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.

test procedure

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.

  1. 01clone10M rows · 894 MB
  2. 02load24 workers
  3. 03migratelock_timeout 1.5s
  4. 04measure250ms samples
  5. 05verdictunsafe > 1s
  1. 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.

  2. 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.

  3. 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.

  4. 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.

honest about the neighbours

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.

runs on zerops

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.

  1. webstatic dashboard
  2. apigo engine · sse
  3. dbpostgres 16 · dedicated vcpu
clone lifecycle
template twin twin discarded

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.

bring your own data

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.

  1. 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
  2. 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
  3. 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