Schema
Sometimes the right answer is no
Before altering a column, SchemaStack counts the rows, classifies the operation against the engine you actually run, estimates how long it will take and names what it will lock — and while it runs, the API says 503 rather than hanging.
Changing a column type in a spreadsheet is instant. Changing one in a 2-million-row Postgres table takes four minutes and blocks every read for the duration.
Most tools in this category paper over that distinction, because the honest version is unpleasant: your click will take four minutes and your application will be down. So they run the ALTER and hope you weren't busy.
We'd rather tell you.
The dry run
Before anything is altered, the change is put through an analyzer that answers four questions in order.
How many rows? Estimated from the table itself, because a type change on 800 rows and the same change on 2.4 million are different events and only one of them needs a plan.
What operation is this, on the engine you actually run? PostgreSQL and MySQL/InnoDB are classified separately, and non-InnoDB MySQL separately again, because they genuinely differ. Some examples that come straight out of the analyzer:
| Change | PostgreSQL | MySQL / InnoDB |
|---|---|---|
| Change a column type | full table rewrite, AccessExclusive — blocks reads and writes | table rebuild, blocks writes only |
Add NOT NULL | full table scan to validate, AccessExclusive | in-place, concurrent writes allowed |
Drop NOT NULL | instant metadata change | in-place |
Shrink a VARCHAR | rewrite if the length actually shrinks | rebuild |
| Add a column with a default | instant on PG 11+, rewrite before that | instant |
How long? Duration is estimated from the row count against separate per-row rates for a rewrite, a full scan and an index build — because those three cost differently and lumping them together produces an estimate you'd be right to ignore.
So what will it lock? The verdict names it: blocks reads and writes, blocks writes only, or neither.
While it runs
A migration that blocks is going to block. The question is what your API does about it.
It answers 503 with a Retry-After header, and a body saying which table is migrating. Not a hung request holding a connection while it waits on a lock, and not a timeout that looks like an outage. A client that understands Retry-After backs off and comes back; one that doesn't at least gets an error it can log properly.
Two rules keep this bounded. Only one blocking migration per table may start — a second is refused rather than queued behind the first, so you can't accumulate a pile-up you didn't intend. And a migration cannot hold a table indefinitely, regardless of how badly its duration was estimated; there's a ceiling, and passing it ends the migration rather than extending the outage.
Why an estimate is still worth having
An estimate that's wrong by a factor of two is still the difference between "do this now" and "do this at 3am". The failure mode we're avoiding isn't imprecision — it's surprise: finding out that a column type change was a four-minute outage by watching it happen.
What it doesn't do (yet)
- Nothing schedules it for you. The dry run tells you this will block reads for four minutes, and then choosing the hour is entirely on you. No maintenance window, no queue, no "apply at".
- The blast radius of a cascading foreign key isn't computed. The guard checks the table you're changing, not the tables a cascade would reach. A change that fans out is estimated as though it doesn't.
- Estimates come from row counts and fixed per-row rates, not from your hardware, your disk, your current load or your Postgres version's specific behaviour. A busy database will be slower than the number says.
503protects data endpoints. It doesn't hold back another tool connecting to your database directly — this is your database, and we don't own the only door to it.- No progress on a running migration. You know it started and you know when it ends.
- No rollback of a completed migration. Reversing a type change is another migration, with its own dry run and its own cost.
- The one-blocking-migration-per-table rule is per table, not per database. Several tables can migrate at once, and the combined load isn't estimated.
The schema migrations guide covers the mechanics, and what happens when the schema moves without us is the other half of this story — changes from outside, rather than changes you asked for.
Verified 22 Aug 2026: 52 tests — the impact analyzer's PostgreSQL, MySQL/InnoDB, non-InnoDB and aggregation cases (27), the migration guard interceptor including its active-blocking-migration behaviour (16), and end-to-end column migrations against a live database covering type changes, renames, constraint changes and multi-change batches (9).