ITADN

`drizzle-kit migrate` runs full commutativity check on every invocation — ~115s on a 70-migration repo, even when no migrations are pending

#5777OpenJeremyCraven 创建于 2026-05-18
J
JeremyCravencommented
## Summary Since `drizzle-kit@1.0.0-beta.18` introduced commutative-migrations checking (per discussion #5005), the `migrate` command calls `checkHandler` unconditionally before applying any migrations. On a project with ~70 migrations and ~28 MB of snapshot.json files, this adds **~115 seconds of single-threaded CPU work to every `drizzle-kit migrate` invocation** — including no-op runs where the DB is already up to date. The `--ignore-conflicts` flag suppresses the report but does not skip the analysis, so it provides no perf relief. **Verified affected versions (this is *not* fixed in the latest published release):** - `1.0.0-beta.22` npm tarball: ~115 s - `1.0.0-rc.2` npm tarball: ~117 s - `1.0.0-rc.3` source (`beta` branch HEAD): same code path present — see `drizzle-kit/src/cli/schema.ts:147` — `await checkHandler(out, dialect, ignoreConflicts);` ## Repro A repo with N migrations where each snapshot.json is ~500 KB (representative of a mature Postgres schema: ~120 tables, indexes, FKs, RLS policies, etc.). At N = 71, total snapshot footprint ≈ 28 MB. ``` $ time pnpm drizzle-kit migrate --config drizzle.config.ts Reading config file 'drizzle.config.ts' Using 'pg' driver for database querying [✓] migrations applied successfully! pnpm drizzle-kit migrate ... 115.01s user 0.90s system 101% cpu 1:54.55 total ``` DB is already migrated; zero migrations applied. All ~115 s is the pre-flight commutativity check. Cost scales roughly linear-in-snapshot-bytes plus a multiplier per branching point in the migration DAG; adding a migration today costs ~+1.5 s on subsequent runs. ## Root cause (verified on `beta` branch HEAD = rc.3 commit) 1. **`drizzle-kit/src/cli/schema.ts:147`** — the `migrate` command's handler calls `await checkHandler(out, dialect, ignoreConflicts);` before any DB connection is opened. 2. **`drizzle-kit/src/cli/commands/check.ts` → `checkHandler`**: - Reads + `JSON.parse`s every snapshot.json (pass 1, validation). - Runs Zod `.strict()` over every snapshot — by far the most expensive line item; the schemas are deeply nested (`schemaInternal`, `tables`, `columns`, `indexes`, `fks`, `checks`, `policies`, …) and `.strict()` walks every field. - Calls `commutativity.detectNonCommutative(snapshots)` → `buildSnapshotGraph(snapshots)` which **reads + parses every snapshot again** (pass 2) to build the DAG. Net: every snapshot is JSON-parsed twice and Zod-validated once per invocation. - For each parent with multiple children, runs full `diffSnapshots` between parent and each leaf, then pairwise across leaves. Linear chains skip this, but a repo that has gone through rebases/rechains has branching points that pay this per-pair cost. 3. **`--ignore-conflicts`** (flag declared in `migrate` and `check` commands) only short-circuits the *report + `process.exit`* in `checkHandler` after `detectNonCommutative` has already returned. The expensive work is unconditional. ## Why this matters - `migrate` is the most frequently invoked drizzle-kit command in a normal dev loop (every time you switch branches, reset your DB, run integration tests against a fresh schema, etc.). Adding ~2 minutes to a command that used to take under a second is a significant regression for local DX. - By the time `migrate` runs, the migration chain has already been merged. Commutativity warnings at apply-time are advisory at best — the conflicting migrations are already in the tree and will be applied in whatever order the journal records. The check belongs in `generate` (where it can prevent the problem) and in the dedicated `drizzle-kit check` command (where users opt into it), not as an unconditional pre-flight on `migrate`. ## Suggested fixes In rough preference order: 1. **Remove `checkHandler` from `migrate`'s handler entirely.** Keep it on `generate` (where it catches the issue at the right time) and on the dedicated `drizzle-kit check` command. CI can run `drizzle-kit check` as a separate step. 2. **If you want to keep a safety net on `migrate`**, gate it behind an opt-in flag (`--check` / `--strict`) rather than running by default. 3. **At minimum, make `--ignore-conflicts` actually short-circuit the analysis** — `if (ignoreConflicts) return emptyResult();` before `detectNonCommutative` — so users have an escape hatch today without pinning back to beta.17. The flag's name already implies this is what it does. 4. **Independently of the gating decision**, the snapshot-load path could be substantially faster: deduplicate the two read+parse passes, and skip Zod `.strict()` for snapshots written by drizzle-kit itself (writer-trusted). Issues #5635 and #5133 are also touching the snapshot-bloat problem from the storage side. Happy to send a PR for (1) or (3) if there's interest. ## Side note: `v1.0.0-rc.2` git tag While investigating this we noticed the `v1.0.0-rc.2` git tag points at commit `48e54060…` (`"Netlify-DB for main (#5663)"`), which is on the `main` branch (the legacy 0.31.x line), not on `beta`. That commit predates the commutativity feature entirely, so source-code searches against the tag read like "feature fixed in rc.2" when in reality the npm tarball published under that version is built from a different (1.0-line) commit and *does* have the feature. Not a perf issue; just a heads-up that it makes "what shipped in rc.2?" hard to answer from source. `v1.0.0-rc.3` is on `beta` and looks correct. ## Environment - `drizzle-kit@1.0.0-rc.2` (also verified beta.22 and inspected rc.3 source) - `drizzle-orm@1.0.0-rc.2` - Node 24, macOS, Postgres 16 - 71 migration files, ~28 MB total snapshot footprint - Migration chain has a handful of branching points from cross-branch rebases
9 条评论