We give every PR a branched Postgres with production-shaped data. The trick is copy-on-write at the storage layer plus aggressive_sleep: branches auto-sleep after 30 idle minutes, wake in ~800ms, and die with the PR. Storage cost per branch is roughly the diff, not the database.
Rules that keep it cheap: seed with a 2% sample plus all lookup tables (nobody needs 40M events to test a migration), block branches from production secrets (separate role, read-only seeds), and run migrations against the branch in CI before touching staging.
Biggest surprise: developers started testing migrations voluntarily once it took one click. The bottleneck was never discipline, it was friction.