drizzle-team / drizzle-team/drizzle-orm
[BUG]: drizzle-kit generate/check is exponential in the number of merged migration branches
- Dominant language
- TypeScript
- Stars
- 35.8k
- Forks
- 1.6k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 4
Description
### Report hasn't been filed before.
- [x] I have verified that the bug I'm about to report hasn't been filed before.
### What version of `drizzle-orm` are you using?
1.0.0-beta.21
### What version of `drizzle-kit` are you using?
1.0.0-beta.21 (also verified present in 1.0.0-beta.24, 1.0.0-rc.3, and 1.0.0-rc.4, the relevant code is unchanged across these releases)
### Other packages
_No response_
### Describe the Bug
#### Undesired behavior
Every drizzle-kit generate (and check) runs the commutativity detector
detectNonCommutative over the whole migration DAG. For each fork point it calls
collectLeaves, a DFS with no visited set, so it enumerates every root-to-leaf
path rather than every distinct leaf. When the migration history contains
merge “diamonds” (created whenever two migrations branch off a common parent and a
later snapshot lists both as parents) the number of paths is exponential in the
number of merges, and the detector runs a full-schema diff once per enumerated
path, all of them redundant.
```
collectLeaves(graph, startId) {
const leaves = [];
const stack = [startId];
const prevToChildren = {};
for (const node of Object.values(graph))
for (const parentId of node.prevIds) (prevToChildren[parentId] ??= []).push(node.id);
while (stack.length) {
const id = stack.pop();
const children = prevToChildren[id] ?? [];
if (children.length === 0) leaves.push(id);
else for (const child of children) stack.push(child); // ← no visited set
}
return leaves;
}
```
#### Environment
- Database engine: PostgreSQL
- Runtime: Bun
- dialect: "postgresql"
#### Steps to reproduce
Initialize a project and generate a baseline migration.
Build K stacked merge diamonds: from the current head, generate two migrations on
two branches off the same parent, then merge so drizzle-kit writes a snapshot whose
prevIds lists both leaf ids. Repeat K times.
Run drizzle-kit generate with no schema change and time it. Runtime grows ~2^K
(each added diamond roughly doubles it), even though it produces nothing.
#### Desired result
generate/check should be roughly linear in (migrations × snapshot size),
independent of how many branches have merged. Any one of these fixes would work I think:
1. Add a visited set to collectLeaves so it returns distinct leaves (O(V+E))
2. Deduplicate leaves before diffing (and/or memoize diffPostgres by
(parentId, leafId)), so a leaf reachable by multiple paths is diffed once.
3. Early-out detectNonCommutative when the global leaf count is ≤ 1 (linear or
fully merged history)
#### Workaround
I was able to work around this by cleaning up the snapshots prevId history to be linear, but of course just a temporary measure, so hoping for a long term fix so we don't need to rely on that.
Full disclosure: this is mostly my AI agent's diagnosis of the issue, it seems right to me but I wanted to open an issue rather put up a fix PR in case it doesn't seem right to you. Thanks!
Contributor guide
Research direction
Locate collectLeaves and detectNonCommutative in drizzle-kit and trace how generate and check invoke them over migration snapshots. Reproduce the behavior with stacked merge diamonds, then ensure each reachable leaf is processed once and rerun generate/check to confirm runtime no longer grows exponentially.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bun, postgresql, typescript
- Domain
- cli, database, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100