Migrations that lock, and the ones that do not
Most of what makes a migration dangerous is which lock it takes and for how long. A few of them take a lock that stops every read on the table, and they look identical in the file.
12 min · runs against the events dataset
Adding a nullable column with no default is instant: it changes the catalog and touches no rows. Adding one with a volatile default rewrites the whole table. In a migration file the two lines are almost identical, and one of them takes an ACCESS EXCLUSIVE lock on your largest table at whatever hour the deploy runs.
ADD COLUMNnullable, no default — instant, catalog only.ADD COLUMNwith a constant default — instant in Postgres 11 and later, a full rewrite before it.CREATE INDEX— blocks writes for its whole duration.CREATE INDEX CONCURRENTLYdoes not, and takes roughly twice as long.ALTER COLUMN … TYPE— a full rewrite, and anACCESS EXCLUSIVElock for all of it.ADD CONSTRAINT … CHECK— scans the whole table.NOT VALIDthenVALIDATE CONSTRAINTdoes it in two cheap steps.
Building an index over a table this size is not instant even here, on a few thousand rows in a browser tab. On a hundred million rows on a production disk it is minutes to hours — and for all of them, without CONCURRENTLY, nothing can write.
Every one of these is a row an ALTER … TYPE would have to rewrite, under a lock that stops reads.
| rows_to_rewritebigint |
|---|
| 8000 |
1 row · 8,000 examined · 91 pages read
The practical defence is a short lock_timeout on migrations and a retry. Failing fast and trying again in ten seconds is almost always better than waiting, because waiting is what turns one slow query into an outage.