Zoltan Kochan c199198e94 perf(pnpr): stream /v1/resolve, and fix the integrated benchmark to actually exercise pnpr (#12237)
Closes #12234.

This PR has two parts. The headline turned out to be the **benchmark**, not the feature.

## Part 1 — `/v1/resolve` streaming (the #12234 feature)

`POST /v1/resolve` now streams **NDJSON** instead of buffering the whole lockfile: one `package` frame per resolved tarball as the server's tree walk yields it, then a terminal `done` (full lockfile + stats), `error`, or `violations` frame. The client fetches each tarball as its frame arrives, overlapping the server's resolution — matching the native `PrefetchingResolver` shape.

- Server: new `ResolutionObserver`/`ObservingResolver` in `package-manager`, threaded through `Install`; `handle_resolve` runs the resolve in a detached task whose observer pushes frames into the response channel; `application/x-ndjson` is excluded from the gzip layer so frames flush incrementally.
- Client: `resolve_streaming(opts, on_package)`; `install_via_pnpr` drives a new `TarballPrefetcher` that warms the shared mem cache as frames arrive.
- Breaking change within protocol v1 (no version bump — experimental pnpr allows it).

**Honest caveat:** streaming only helps when the *server's* resolve is slow (cold/distant server). Against a warm server it's inert — see the results below (`pnpr@HEAD` ≈ `pnpr@main` everywhere). Whether to keep this commit or defer it is an open question; the benchmark fixes below stand on their own.

## Part 2 — make the integrated benchmark actually measure pnpr

While validating Part 1, the benchmark turned out not to be exercising pnpr **at all**, plus it was serving the registry far faster than reality. Fixes:

- **`pnpr@<rev>` targets never routed through pnpr.** `.pnpr-env` exported a bare `PNPR_SERVER`, but pacquet reads config env vars only under the `PNPM_CONFIG_*` prefix, so `config.pnpr_server` was always `None` and every pnpr row was a silent duplicate of its direct row. Fixed the env var name; added a post-run guard that fails the benchmark if a pnpr target's `pnpr-storage` is empty (proof it never served a resolve).
- **Emulate a real registry link for every client.** The latency proxy modeled RTT but not throughput, and fronted only direct targets. Generalized it to a `LinkProfile` (one-way delay + per-direction bandwidth cap), added `--registry-bandwidth-mbps`, and routed *all* registry traffic through it (direct installs, the pnpr server's resolve, the pnpr client's fetches) so the registry-mock is uniformly as remote as real npm. CI runs it at 50 ms + 200 Mbit/s (≈ the measured public-npm peak).
- **Make "cold cache" cold for resolution.** Forced `cacheDir` bench-local and wipe it in cold-cache scenarios, so a direct install actually pays the packument-fetch waterfall (previously the global metadata mirror survived every wipe).
- **New scenario** `fresh-install.cold-cache.hot-store` that isolates resolution (cold metadata, hot store → no download to mask it).

## Results (Linux CI, after the fixes)

| Scenario | pacquet@HEAD | pnpr@HEAD | pnpr speedup |
|---|---:|---:|---:|
| fresh-install · cold cache · **hot store** | 5.06 s | 0.69 s | **7.4×** |
| fresh-install · cold cache · cold store | 5.36 s | 2.03 s | **2.65×** |
| fresh-install · hot cache · hot store | 1.46 s | 0.69 s | **2.1×** |
| fresh-restore (frozen) · cold cache · cold store | 10.08 s | 5.13 s | **2.0×** |
| fresh-restore (frozen) · hot cache · hot store | 0.71 s | 0.80 s | 0.89× (slower) |

pnpr offloads the client's resolution (and, on the frozen path, lockfile verification) to its warm server: 2–7× faster wherever the client would otherwise pay that cost. The lone regression is the fully-warm frozen install, where there's nothing to offload and pnpr's one round trip is pure overhead. `pnpr@HEAD` vs `pnpr@main` is flat throughout — i.e. the streaming commit (Part 1) adds ~nothing against a warm server, while the base pnpr win (offload to a warm server) is large.
2026-06-06 15:25:08 +02:00
2026-04-10 18:30:33 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-04-30 23:03:46 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-05-24 02:23:07 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-04-30 23:19:31 +02:00
2026-06-05 08:27:41 +02:00
2026-06-05 08:27:41 +02:00
2026-04-30 23:03:46 +02:00
2026-06-05 08:27:41 +02:00
2026-01-16 16:31:31 +01:00
2024-03-21 01:09:22 +01:00

简体中文 | 日本語 | 한국어 | Italiano | Português Brasileiro

pnpm

Fast, disk space efficient package manager:

  • Fast. Up to 2x faster than the alternatives (see benchmark).
  • Efficient. Files inside node_modules are linked from a single content-addressable storage.
  • Great for monorepos.
  • Strict. A package can access only dependencies that are specified in its package.json.
  • Deterministic. Has a lockfile called pnpm-lock.yaml.
  • Works as a Node.js version manager. See pnpm runtime.
  • Works everywhere. Supports Windows, Linux, and macOS.
  • Battle-tested. Used in production by teams of all sizes since 2016.
  • See the full feature comparison with npm and Yarn.

To quote the Rush team:

Microsoft uses pnpm in Rush repos with hundreds of projects and hundreds of PRs per day, and weve found it to be very fast and reliable.

npm version OpenCollective OpenCollective X Follow Stand With Ukraine

Platinum Sponsors

Bit

Gold Sponsors

Sanity Discord Vite
SerpApi CodeRabbit Stackblitz
Workleap Nx

Silver Sponsors

Replit Cybozu devowl.io
u|screen Leniolabs_ Depot
Cerbos ⏱️ Time.now

Support this project by becoming a sponsor.

Background

pnpm uses a content-addressable filesystem to store all files from all module directories on a disk. When using npm, if you have 100 projects using lodash, you will have 100 copies of lodash on disk. With pnpm, lodash will be stored in a content-addressable storage, so:

  1. If you depend on different versions of lodash, only the files that differ are added to the store. If lodash has 100 files, and a new version has a change only in one of those files, pnpm update will only add 1 new file to the storage.
  2. All the files are saved in a single place on the disk. When packages are installed, their files are linked from that single place consuming no additional disk space. Linking is performed using either hard-links or reflinks (copy-on-write).

As a result, you save gigabytes of space on your disk and you have a lot faster installations! If you'd like more details about the unique node_modules structure that pnpm creates and why it works fine with the Node.js ecosystem, read this small article: Flat node_modules is not the only way.

💖 Like this project? Let people know with a tweet

Getting Started

Benchmark

pnpm is up to 2x faster than npm and Yarn classic. See all benchmarks here.

Benchmarks on an app with lots of dependencies:

License

MIT, except the pnpr/ directory, which is source-available under the PolyForm Shield License 1.0.0.

Description
No description provided
Readme MIT 280 MiB
Languages
Rust 56.4%
TypeScript 43%
JavaScript 0.5%