You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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)
drizzle-kit/src/cli/schema.ts:147 — the migrate command's handler calls await checkHandler(out, dialect, ignoreConflicts); before any DB connection is opened.
Reads + JSON.parses 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.
--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:
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.
If you want to keep a safety net on migrate, gate it behind an opt-in flag (--check / --strict) rather than running by default.
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.
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
Summary
Since
drizzle-kit@1.0.0-beta.18introduced commutative-migrations checking (per discussion #5005), themigratecommand callscheckHandlerunconditionally 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 everydrizzle-kit migrateinvocation — including no-op runs where the DB is already up to date.The
--ignore-conflictsflag 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.22npm tarball: ~115 s1.0.0-rc.2npm tarball: ~117 s1.0.0-rc.3source (betabranch HEAD): same code path present — seedrizzle-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.
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
betabranch HEAD = rc.3 commit)drizzle-kit/src/cli/schema.ts:147— themigratecommand's handler callsawait checkHandler(out, dialect, ignoreConflicts);before any DB connection is opened.drizzle-kit/src/cli/commands/check.ts→checkHandler:JSON.parses every snapshot.json (pass 1, validation)..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.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.diffSnapshotsbetween 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.--ignore-conflicts(flag declared inmigrateandcheckcommands) only short-circuits the report +process.exitincheckHandlerafterdetectNonCommutativehas already returned. The expensive work is unconditional.Why this matters
migrateis 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.migrateruns, 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 ingenerate(where it can prevent the problem) and in the dedicateddrizzle-kit checkcommand (where users opt into it), not as an unconditional pre-flight onmigrate.Suggested fixes
In rough preference order:
checkHandlerfrommigrate's handler entirely. Keep it ongenerate(where it catches the issue at the right time) and on the dedicateddrizzle-kit checkcommand. CI can rundrizzle-kit checkas a separate step.migrate, gate it behind an opt-in flag (--check/--strict) rather than running by default.--ignore-conflictsactually short-circuit the analysis —if (ignoreConflicts) return emptyResult();beforedetectNonCommutative— so users have an escape hatch today without pinning back to beta.17. The flag's name already implies this is what it does..strict()for snapshots written by drizzle-kit itself (writer-trusted). Issues Feature: delta-encoded snapshot.json to eliminate redundant storage #5635 and [FEATURE]: Minify Snapshot Files #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.2git tagWhile investigating this we noticed the
v1.0.0-rc.2git tag points at commit48e54060…("Netlify-DB for main (#5663)"), which is on themainbranch (the legacy 0.31.x line), not onbeta. 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.3is onbetaand 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