DEV Community

Kelvyn Thai
Kelvyn Thai

Posted on

PNPM Content-Addressable Store Looks Broken on macOS

Hi everyone, yesterday I spent time migrating from Yarn to pnpm, hoping for potential memory savings from the content-addressable store (global store) and faster package installation. Everything was smooth — pnpm consistently outperformed Yarn in both cached and uncached cases.

But then I encountered something strange on macOS: it seemed like the Content-Addressable Store was broken.

I spent many hours debugging on macOS using the Storage UI, CLI (du -sh node_modules), etc., but nothing worked. As you can see in the picture, the size of node_modules is still the same, right?

In my head, I was still thinking: “Hmm… if our repositories can share packages from the global store, why is node_modules still so large?” Then I switched to Ubuntu, and voilà — it worked as expected. In the Storage UI, the size difference was clear: only ~6 MB per repository.

But wait a minute — after using the CLI command du -sh node_modules, the result was still the same on macOS… Hmm, maybe I misunderstood how the content-addressable store works.

I went to the pnpm GitHub issues and found some related discussions:
https://github.com/pnpm/pnpm/issues/2761
https://github.com/pnpm/pnpm/issues/9935

And then I realized: oh, I was really wrong about the definition of a content-addressable global store. So I spent more time reading about hardlinks and symlinks, and voilà — I found the root cause.

In my case, let’s say you have lodash duplicated in two repositories. pnpm does two “magic” things:

  1. Hardlink: It creates a hardlink version of lodash (only for files, not folders).
  • Why hardlink? Because a hardlink allows you to create a clone of a file (not a folder) without increasing your storage usage. It only increases the link count (similar to a linked list) when you add or clone another version.
  1. Symlink: It creates a symlink (shortcut).
  • Why symlink? Because Node.js module resolution (see official docs) relies on folder paths, not files, when you use require (CJS) or import (ESM).

Then I tested again on Ubuntu by checking the hardlink count with the CLI, and I saw 3 hardlink versions (one from pnpm-test, one from pnpm-test-1, and the original one from the global store) with same 828284 Inode number — Fanstatic!

ls -lai ./node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/package.json
Enter fullscreen mode Exit fullscreen mode

But back on macOS, it only showed 1 and with a different inode. That means it’s not working as expected — this version did not clone from the global store.

Top comments (0)