Presift

Checking MySQL migrations in CI without a dev database

Most migration-safety tooling wants a database. It starts MySQL in your pipeline, replays the migration against it and reports what would happen. That is the more thorough approach — and for a lot of teams it is also the reason the check never gets added. This page is about the other option: deciding whether a migration is risky from the statement text alone, what that buys you, and exactly what it cannot tell you.

The change that starts the conversation

A column needs to hold emoji, so the table's character set changes:

ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

One line, reviewed in seconds, approved by two people. The MySQL 8.0 manual's online DDL operations table lists converting a character set as in place: yes, rebuilds table: yes, permits concurrent DML: no. On a large, busy users table that is a table rewrite with writes blocked for the duration — and the information was in the manual the whole time.

The same holds for a handful of other statement classes: ADD FOREIGN KEY (a copy unless foreign_key_checks is disabled), MODIFY COLUMN when the data type really changes, adding a FULLTEXT or SPATIAL index, primary-key changes, ENGINE= and ROW_FORMAT=, and anything destructive — DROP TABLE, DROP COLUMN, TRUNCATE. MySQL 8.0's atomic DDL is not transactional DDL: a DDL statement implicitly commits the session's transaction before it runs, so a later ROLLBACK cannot undo one that has already executed.

Two ways to check this in a pull request

With a database in CIFrom the statement text
What it can seeyour actual schema and column definitions — it replays the changethe statement, and the MySQL rules that apply to it
What the job needsa MySQL instance, commonly an ephemeral Docker container, plus the time to start itnothing beyond the runner
Precisionhigher — it can tell you what your column is todaylower on anything that depends on the current definition
Still invisibletable size, production traffic, replication lagall of the above, plus the current schema, the server version and foreign_key_checks
Typical failurethe check never gets added, because someone has to own a database in the pipelinea finding that says "potentially" and needs a human to confirm

Neither approach tells you how long a rebuild will take on your table. Table size is the most useful input to that question and no static check has it.

What a statement-only check actually reports

Real output from Presift, the tool behind this site, on the migration above:

✖ MG002 ERROR  V12__charset.sql:1
    ALTER TABLE users: CONVERT TO CHARACTER SET rebuilds the table and blocks concurrent DML
    Why: Converting a table's character set rewrites every row (the manual lists it as in place,
         rebuilds table, permits concurrent DML: No); writes are blocked for the duration.
    Do:  Avoid on large/busy tables during traffic. Options: expand/contract; gh-ost /
         pt-online-schema-change; or a maintenance window. Assert the expectation explicitly with
         ALGORITHM=... so MySQL fails fast if it cannot comply.
    Ref: https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html

Just as important is what it does not report. All three of these pass with no findings:

ALTER TABLE orders ADD INDEX idx_customer_created (customer_id, created_at), ALGORITHM=INPLACE, LOCK=NONE;
ALTER TABLE orders ADD COLUMN referral_code VARCHAR(32) NULL;
CREATE TABLE audit_log (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, ...) ENGINE=InnoDB;

A change you have decided to make anyway is annotated in the migration itself, with a reason the next reviewer can read:

-- presift: allow MG001 replaced by orders_v2 in migration 0141, verified unused for 30 days
DROP TABLE orders_legacy;

Where the outcome depends on a column's existing definition — MODIFY COLUMN is the common case — the finding says so and is marked medium confidence rather than claiming certainty it cannot have.

How this compares with the tools you have probably already seen

Written as fairly as we can manage. Each of these does things Presift does not.

ToolWhat it isMySQL lock/rebuild analysisNeeds a database?Cost
Atlasschema-as-code, versioned migrations, linting, drift and policy — far broader than a linteryes, and more thorough than ours: its MySQL analyzers cover the same statement classes, and its dev-database simulation sees your actual schemayes — atlas migrate lint takes a --dev-url, usually an ephemeral Docker instanceits lint command moved out of the free plan in v0.38 (2025-10-28); Pro is $9 per developer per month plus $59 per month per CI project
Skeemadeclarative pure-SQL schema management for MySQL and MariaDBpartly — unsafe changes are blocked by default, and it has linter checksyes — changes are verified in a workspace on a real serverCommunity free; Premium from $124 per month
Bytebasea database governance platform with 200+ SQL review rulespartly; rules that need row counts query the databaseyes — it is a deployed platform connected to your databasesCommunity free up to 20 users and 10 instances; Pro $20 per user per month
Squawkthe linter this whole category is measured againstnofree and open source
Presiftfour MySQL rules applied to the .sql files in the pull requestyes, from the statement text onlyno — no database, no platform, no account30-day evaluation, then paid — below

Squawk is PostgreSQL. If you are on Postgres, use it: it is free, open source and well established, and Presift has nothing to offer you. The gap this page is about is MySQL-specific.

What it costs, stated plainly

Presift is not free software. Inside GitHub Actions a 30-day evaluation starts by itself when the job has permissions: id-token: write — no key to request, no form, no account, no card — and it runs the complete product. After that, continued use needs an organisation licence, intended at $290 per organisation per year. That is an initial pricing hypothesis, subject to validation: there is no checkout yet, no payment processor connected and no customer. If that is a dealbreaker, the honest answer is to use Atlas — it does more, and its rules are published.

Trying it takes one workflow

name: migration-safety
on: [pull_request]
permissions:
  contents: read
  id-token: write          # lets the 30-day evaluation start
jobs:
  presift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: nishu-sde/presift@v1
        with:
          paths: db/migrations
          fail-on: error

Linux x86_64 runners with Python 3.9+ (GitHub-hosted ubuntu-* qualify). Reads plain SQL, Flyway, Liquibase formatted SQL, and the SQL generated by Prisma Migrate, Drizzle Kit, goose, dbmate and golang-migrate. Fork pull requests cannot start an evaluation, because GitHub issues them no identity token, and outside GitHub Actions the evaluation is not available.

How the evaluation works The client is open source — read it CI setup and options

If you take nothing else from this page

Look up every ALTER TABLE in your next migration in the online DDL operations table and read three columns: in place, rebuilds table, permits concurrent DML. Append , ALGORITHM=INPLACE, LOCK=NONE to the ones you expect to be online, so MySQL fails fast instead of quietly choosing a copy. That costs nothing and prevents most of this. A tool is only worth adding if it does it for you on every pull request, forever, without anyone having to remember.