This version is in beta. Some features may change before release.

Checking migrations for zero-downtime safety

The checkmigrations command flags destructive or non-atomic operations before you deploy without a maintenance window.

Checking migrations for zero-downtime safety

When you deploy multiple times a day, a migration runs while the old code is still serving traffic. Some schema changes are fine in that window (adding a nullable column); others break it (dropping a column the old code still reads) or aren't atomic with the code rollout (renaming a table). umbral checkmigrations walks every pending operation and tags it so you know which is which before you ship.

Info
This is read-only triage; it applies nothing. `migrate` still runs every operation exactly as written. `checkmigrations` is the gate you put in CI *before* `migrate`.

Running it

Code
bash
umbral checkmigrations
Code
text
Checking 4 operation(s) across 2 pending migration(s)...
 
UNSAFE (1):
[DROP COL] app/0007_drop_legacy: drops column `order.legacy_total` and its data; old code reading it breaks. Expand-contract: stop writing it, deploy, then drop
 
WARNING (1):
[RENAME COL] app/0006_rename: renames column `order.total` -> `amount`; old code references `total`. Expand-contract: add `amount`, backfill, switch reads, then drop `total`
 
Summary: 2 safe, 1 warning, 1 unsafe.

The command exits non-zero when any UNSAFE operation is present, so it fails a CI pipeline by default. Add --strict to also fail on WARNING-tier operations (e.g. when even a column rename must be reviewed by a human first):

Code
bash
umbral checkmigrations --strict

The three tiers

SAFE: additive and backward-compatible

CREATE TABLE, a new M2M join table, and adding a nullable column (or a NOT NULL column with a default). Old code keeps working untouched.

WARNING: review before deploying

RENAME TABLE / RENAME COLUMN (not atomic with a code deploy, since one code version references the missing name), ALTER COLUMN (a type change rewrites the column and locks a large table; a NOT NULL tightening fails on existing NULLs), and adding a NOT NULL column with no default (old inserts that omit it fail).

UNSAFE: destructive or irreversible

`DROP TABLE`, `DROP COLUMN`, `DROP` of an M2M join table. Data is gone the moment it runs, and old code reading the dropped surface errors immediately.

The expand-contract pattern

The notes on each non-safe operation point at expand-contract, the standard way to make a breaking change in two safe deploys instead of one breaking one:

  1. Expand. Add the new shape alongside the old (add the new column, dual-write to both). This migration is SAFE.
  2. Migrate the data and switch reads to the new shape, then deploy the code that only uses it.
  3. Contract. Drop the old shape in a later migration, once no running code references it. This migration is UNSAFE, but by now it's safe in practice because nothing reads the old surface.

Using it programmatically

The classification is a pure function, so a plugin or a custom command can gate its own deploys:

Code
rust
use umbral::migrate::{check_pending_safety, classify_operation, OpSafety};
 
let pending = check_pending_safety().await?;
let blockers = pending.iter().filter(|c| c.safety.is_unsafe()).count();
if blockers > 0 {
// refuse to proceed, page a human, etc.
}

See also

migrationsclideployment