Migration-safety analysis checks a schema migration against a rule set of known hazards, and flags the statements that are unsafe to run online. The migration runs against a live database while the application keeps serving, and the hazard is rarely that the SQL is wrong. An operation that holds a lock blocking reads and writes for more than a moment turns a routine deploy into an outage, and a backward-incompatible change breaks the code still running during a rolling deploy.
It is a linter applied to migration files rather than application
code: it checks each migration's operations against rules such as "no index
built without CONCURRENTLY" or "no NOT NULL added to a populated column"
before the migration reaches production. What separates it from a general linter is the property it checks —
database availability through the change, not a code-quality pattern.
What it catches¶
- Table-locking DDL. An
ALTER TABLEthat adds a constraint withoutNOT VALIDscans every existing row while it holds its lock, which on Postgres blocks reads and writes for most constraint kinds. - Non-concurrent index builds.
CREATE INDEXwithoutCONCURRENTLYon Postgres blocks writes until the index is built. - Backward-incompatible changes. Dropping or renaming a column, or adding a
NOT NULLcolumn without a default, breaks the code still running mid-rollout. - Full-table rewrites. A column type change or a volatile default rewrites every row under a lock.
Approaches and tools¶
strong_migrations checks Rails and ActiveRecord
migrations against Postgres, MySQL, and MariaDB rules, and prints the safe
rewrite for each dangerous operation. squawk lints raw Postgres
SQL, so it works whatever generated the migration, and runs as a CLI, a
pre-commit hook, or a pull-request bot. django-migration-linter
flags backward-incompatible Django migrations in CI. Atlas offers a
migrate lint subcommand for schema as code across several engines (an Atlas
Pro feature; the open-source Community Edition omits it).
When to use, when not¶
Use:
- To gate every migration in CI on a service that deploys without a maintenance window. The check is cheap and the failure it prevents is an outage.
- On raw-SQL Postgres migrations regardless of ORM, where squawk parses the DDL directly.
- To hold teams to the expand-and-contract discipline a rolling deploy needs: add the new shape, backfill, switch reads, drop the old shape.
Don't:
- On a system that can take a maintenance window or serves no concurrent traffic: the lock a linter forbids is harmless when nothing is reading or writing.
- As a substitute for testing the migration's data transformation. The linter checks how the change is applied, not whether the resulting data is correct.
Evidence¶
The case is operational rather than drawn from controlled studies. A lock that blocks reads and writes for more than a moment is a service outage by definition, and every mainstream database documents which DDL statements acquire one. The linters encode that per-engine lock behavior as rules, so their reach is bounded by the rule set: a migration whose danger the rules do not model still needs review.
Classification¶
- Quality dimensions: Reliability.
- Area: Database schema migrations (DDL) under rolling or zero-downtime deploys: ORM migration files and raw SQL for Postgres, MySQL, MariaDB.
- Guarantee: Empirical: a fixed set of known-unsafe patterns per database engine, tuned for false-positive rate; a novel unsafe migration outside the rules passes.
Referenced by¶
- Static analysis · Methods