drizzle-team / drizzle-team/drizzle-orm

[BUG]: drizzle-kit generate/check is exponential in the number of merged migration branches

Open
#5,960 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.