Repository navigation
Description
Verify latest release
- I verified that the issue exists in the latest pnpm release
pnpm version
12.15.0
Which area(s) of pnpm are affected? (leave empty if unsure)
Dependencies resolver, Lockfile
Link to the code that reproduces this issue or a replay of the bug
Reproduction steps
Clone the repository above, then run the script with a native pnpm 12.15.0 executable:
scripts/new-importers.sh /path/to/pnpm 1 2 4 8 16
scripts/new-importers.sh /path/to/pnpm --existing 16The script adds N workspace projects under packages/new-importers/. Each one depends only on
"@synth/pkg-00001": "workspace:*". It then runs:
pnpm install --lockfile-only --trust-lockfile --prefer-offline --ignore-scripts --reporter=append-onlyWith --existing, the script instead gives N existing projects the same dependency, as a control.
It restores the committed files after each run. The 16 projects in pnpm-only/ depend on public
@atlaskit editor packages with different react / react-intl providers. This makes the lockfile
peer-heavy: 3,803 snapshots, of which 2,040 have a peer suffix and 1,178 list
transitivePeerDependencies.
Describe the Bug
Summary
Every workspace project that the lockfile does not list yet adds a fixed amount of resolution time,
whatever its dependencies are. Adding the same dependency to existing projects costs nothing extra.
The numbers are the median resolve_workspace from TRACE=pacquet::install::phase=info, over 3 rounds. They
were measured on a Mac M4 Pro with native 12.15.0 binaries, the command above and a warm metadata cache.
Reproduction:
resolve_workspace |
|
|---|---|
| 1 new project | 4.4 s |
| 2 new projects | 5.0 s |
| 4 new projects | 5.8 s |
| 8 new projects | 7.6 s |
| 16 new projects | 11.8 s |
| 16 existing projects gain the same dependency | 4.2 s |
Our private monorepo has 13.5k importers and a lockfile with 22.5k snapshots, 1.8k of them hashed.
The same script there (new projects depend only on one workspace package) gives:
resolve_workspace |
Wall time | |
|---|---|---|
| 1 new project | 36.6 s | 61.5 s |
| 16 new projects | 354.5 s | 378.6 s |
| 16 existing projects gain the same dependency | 14.0 s | 39.8 s |
Each new project adds about 21 s there, so 16 new projects take over 6 minutes to resolve. A real PR that
added 9 small projects made our merge-queue lockfile update exceed its 5-minute limit.
Where the time goes
ImporterHoistState::init calls LockedPeers::of, which calls importer_locked_peer_versions
(resolve_importer/locked_peers.rs). When the importer is not in the wanted lockfile, that
falls back to all_locked_peer_versions(lockfile). The fallback walks every snapshot and recovers
the peers of every hashed suffix. It is computed once per importer and not shared, so N new
importers repeat the whole-lockfile scan N times.
We timed it on our monorepo with log lines added around LockedPeers::of and extend_tree. This was a
12.12.1 build, but 12.15.0 takes the same fallback for each importer.
LockedPeers::oftook 316 s of a 353 sresolve_workspacewith 9 new projects.- That was 31–43 s for each new importer.
- Every existing importer took under 0.7 s.
extend_treefor the new importers took about 0.1 s each.
#17139 memoises hashed peer bindings within one scan, but the scan still runs once for
each new importer.
Expected Behavior
The locked peer versions of an importer the lockfile does not know should be derived once per
resolution and shared, so that adding N projects costs about the same as adding one.
Which Node.js version are you using?
24.11.1
Which operating systems have you used?
- macOS
- Windows
- Linux
If your OS is a Linux based, which one it is? (Include the version if relevant)
No response
Contributing a fix
- I am working on a fix
Activity
[-]performance: each new workspace project rescans every locked peer, so adding projects makes lockfile updates take minutes[/-][+](Bug) performance: each new workspace project rescans every locked peer, so adding projects makes lockfile updates take minutes[/+]samanyudas commentedon Oct 24, 2026
I'm working on a fix for this.
perf(deps-resolver): share the locked peers of importers the lockfile…
perf(deps-resolver): share the locked peers of importers the lockfile…
Update pnpm to v12.16.1 (#2164)