소개
pnpm은 NPM의 성능을 개선하기 위해 만들어진 패키지 매니저입니다. pnpm은 기본적으로 패키지를 하드 링크로 관리하여, 패키지가 중복 저장되지 않도록 최적화합니다. 이 방식은 디스크 사용량을 줄이고 설치 속도를 크게 향상시킵니다.
특징
- pnpm은 패키지를 중복 저장하지 않으며, 하드 링크를 사용하여 디스크 공간을 절약합니다.
- pnpm은 패키지를 로컬에 중복으로 설치하는 대신, 중앙 저장소를 사용하여 모든 패키지를 한 번만 다운로드하고, 그 후 프로젝트별로 하드 링크를 통해 패키지를 연결
- pnpm은 프로젝트별로 패키지를 격리된 폴더에 설치하며, 덕분에 의존성 충돌을 방지할 수 있습니다. 패키지는 실제로 중복 설치되지 않고, 하드 링크를 통해 연결됩니다.
- 의존성 관리와 패키지 설치가 매우 빠릅니다.
- node_modules 방식에 의존하기 때문에 기본적으로 node_modules 폴더를 유지
추가 설명
하드링크 & 심볼릭 링크 사용
특성
참조 대상 | 파일의 데이터 블록 (inode) | 파일의 경로 (파일 시스템의 경로) |
파일 시스템 내 위치 | 같은 파일 시스템 내에서만 사용 가능 | 다른 파일 시스템이나 파티션에서도 사용 가능 |
원본 파일 삭제 시 | 원본 파일 삭제 후에도 하드 링크는 유지되며 데이터에 접근 가능 | 원본 파일이 삭제되면 심볼릭 링크는 깨진 링크가 되어 사용할 수 없음 |
inode 공유 여부 | 원본 파일과 동일한 inode를 공유 | 별도의 inode를 가짐 |
디렉토리 링크 | 디렉토리에 하드 링크 불가 | 디렉토리에 심볼릭 링크 가능 |
사용 용도 | 동일 파일 시스템 내에서 디스크 공간 절약 및 데이터 공유가 필요할 때 | 경로를 참조하거나 다른 파일 시스템에 링크를 만들 때 |
예시
- pnpm install 명령을 실행하면, 필요한 패키지를 중앙 저장소에 다운로드하고, 각 프로젝트에 하드 링크를 만들어 패키지를 사용할 수 있게 합니다.
- 패키지가 중앙 저장소에 이미 존재하면, 하드 링크만 생성하여 데이터를 중복으로 저장하지 않도록 합니다.
pnpm의 하드 링크 사용
pnpm은 하드 링크를 사용하여 의존성 패키지를 로컬의 중앙 캐시와 프로젝트의 node_modules 간에 중복 저장하지 않고, 데이터 블록을 공유합니다. 이렇게 하면 동일한 패키지를 여러 번 저장하지 않고도 여러 프로젝트에서 사용할 수 있어, 디스크 공간이 절약됩니다.
하드 링크는 pnpm이 패키지 데이터를 중앙 저장소와 각 프로젝트에서 공유할 수 있도록 돕습니다.
- 프로젝트에서 패키지가 필요할 때, pnpm은 해당 패키지가 중앙 저장소(store)에 이미 존재하는 경우, 그 데이터를 복사하지 않고, 하드 링크를 통해 프로젝트의 node_modules에 연결합니다.
- 하드 링크는 원본 데이터와 동일한 데이터 블록을 가리키므로, 여러 프로젝트에서 동일한 패키지를 사용하더라도 실제 디스크 공간은 중복되지 않습니다. 동일한 inode(파일 시스템에서 파일을 식별하는 고유 번호)를 공유합니다.
- 하드 링크 덕분에 각 프로젝트는 패키지를 별도로 복사하지 않아도 실제로 패키지가 있는 것처럼 사용할 수 있습니다.
pnpm의 심볼릭 링크 사용
https://pnpm.io/ko/symlinked-node-modules-structure
pnpm은 프로젝트의 node_modules 구조를 만들 때 심볼릭 링크를 사용해요. 심볼릭 링크는 각 프로젝트가 패키지를 빠르고 효율적으로 참조할 수 있도록 도와줘요.
!!! 패키지에서 다른 패키지를 참조하는 경우 심볼릭 링크를 생성한다.
pnpm은 기본적으로 패키지 관리에 있어 npm이나 Yarn과는 다른 방식으로 node_modules 구조를 관리합니다. 일반적으로 npm은 node_modules에 각 패키지를 중첩된 폴더 구조로 저장하지만, pnpm은 패키지 간 중복을 방지하기 위해 조금 더 복잡한 구조를 사용합니다. 이때, 심볼릭 링크가 필요합니다.
- pnpm은 각 패키지를 격리된 형태로 관리하기 때문에, 의존성 트리 상에서 서로 다른 패키지들이 특정 패키지를 필요로 할 때, 심볼릭 링크를 사용해 해당 패키지를 가리킵니다.
- 즉, 특정 패키지가 여러 다른 패키지에 의해 참조될 경우, 중복된 패키지를 저장하지 않고 심볼릭 링크로 해당 패키지를 참조합니다.
node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ ├── bar -> <store>/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
├── foo@1.0.0
│ └── node_modules
│ ├── foo -> <store>/foo
│ ├── bar -> ../../bar@1.0.0/node_modules/bar
│ └── qar -> ../../qar@2.0.0/node_modules/qar
└── qar@2.0.0
└── node_modules
└── qar -> <store>/qar
심볼릭 링크의 구조
- 프로젝트의 node_modules 폴더는 패키지 이름만 존재하고, 실제 패키지는 중앙 저장소에서 하드 링크로 참조된 후, 이 폴더 내의 심볼릭 링크를 통해 접근할 수 있게 됩니다.
- 예를 들어, node_modules/react는 실제로는 중앙 저장소에 있는 패키지로의 심볼릭 링크로 작동하여, 프로젝트가 패키지 간 의존성을 관리할 수 있도록 돕습니다.
- node_modules 내에서 각 패키지나 의존성을 중앙 저장소에서 가져와 심볼릭 링크로 연결해 두어, 각 패키지들이 서로 참조할 수 있게 만듭니다.
심볼릭 링크와 하드 링크의 결합
- 하드 링크는 중앙 저장소에 있는 패키지 데이터를 프로젝트로 공유할 때 사용되고,
- 심볼릭 링크는 패키지 의존성 트리를 관리하고, 중복을 줄이기 위해 사용됩니다.
예시 흐름
- 프로젝트 A에서 패키지 react를 설치한다고 가정.
- pnpm은 react 패키지를 중앙 저장소에 다운로드(또는 이미 있으면 생략)하고, 하드 링크를 사용해 프로젝트 A의 node_modules/.pnpm 디렉토리에 저장합니다.
- 그리고, 프로젝트 A의 node_modules/react는 react 패키지로의 심볼릭 링크로 연결되어, 프로젝트 A가 해당 패키지를 사용할 수 있도록 합니다.
- 프로젝트 B도 같은 react 패키지를 사용하면, pnpm은 새로 패키지를 다운로드하지 않고, 하드 링크를 사용해 동일한 패키지를 참조합니다.
폴더구조로 예시
1. 중앙 저장소(store)에 패키지 저장
먼저 pnpm 이 패키지를 다운로드하면 로컬 중앙 저장소에 (예: ~/.pnpm-store 또는 ~/.local/share/pnpm/store)에 저장됩니다. 예를 들어, react 패키지를 설치하면, 중앙 저장소에 다음과 같이 저장돼요.
~/.pnpm-store/v3/
├── files/
├── sha512/12/ab34... (여기에 react 패키지의 실제 파일들이 있음)
이 폴더는 중앙 저장소에요. pnpm은 여기에 패키지를 다운로드해 저장해요. 각 패키지는 해시값을 기반으로 저장되며, 여러 프로젝트가 이 중앙 저장소의 패키지 데이터를 참조하게 돼요.
2. 프로젝트에서 패키지를 가져올때 (하드링크)
이제 react 패키지를 사용하는 프로젝트가 있다고 가정해봐요. 프로젝트 디렉토리에 pnpm install react를 실행하면, pnpm은 중앙 저장소에 이미 다운로드된 패키지 데이터를 하드 링크로 가져와요.
project/
├── node_modules/
├── .pnpm/
├── react@17.0.2/
├── package.json
├── index.js
├── ... (react 패키지의 실제 파일들이 여기에 있음, 중앙 저장소와 하드 링크로 연결됨)
이때, node_modules/.pnpm/react@17.0.2/ 디렉토리의 모든 파일들은 중앙 저장소와 하드 링크로 연결됩니다. 따라서, 프로젝트가 패키지를 사용하더라도 실제 데이터는 중앙 저장소에 존재하며, 디스크 공간을 추가로 사용하지 않습니다.
3. 심볼릭 링크로 패키지 의존성 관리
심볼릭은 특수한 상황에서 사용이 돼요. 로컬 패키지 연결이나, 다른 파일 시스템 간에 패키지 연결에 사용이 됩니다.
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
foo가 bar에 의존할 때, npm에서는 아래와 같이 구성된 구조가
pnpm에서는 아래와 같이 구성된다.
-> - a symlink (or junction on Windows)
node_modules
├─ foo -> .registry.npmjs.org/foo/1.0.0/node_modules/foo
└─ .pnpm
└─ .registry.npmjs.org
├─ foo/1.0.0/node_modules
| ├─ bar -> ../../bar/2.0.0/node_modules/bar
| └─ foo
| ├─ index.js
| └─ package.json
└─ bar/2.0.0/node_modules
└─ bar
├─ index.js
└─ package.json
- pnpm은 node_modules/.pnpm/.registry.npmjs.org/foo/1.0.0/node_modules/foo/index.js와 같은 경로에 실제 패키지를 저장하지만, node_modules/foo에 심볼릭 링크를 생성해 이를 참조하게 합니다.
- 만약 bar이 또 패키지 c에 의존한다면? foo가 bar을 의존하는 구조처럼 상위 bar/2.0.0/node_modules 안에 c에 대한 symlink가 형성될 것이고, c는 .registry.npmjs.org 아래 형성될 것이다.→ deep dependency tree를 형성하지 않는다.
- bar는 직접 node_modules 아래에 없지만, foo를 통해 접근이 가능하다
- foo의 node_modules에 bar에 대한 심링크가 있기 때문에 foo 내부의 코드는 bar를 require할 수 있음
- 대신 bar를 직접 require을 하지 못함
전체 구조 정리
- 중앙 저장소 (store): 패키지의 원본 데이터를 저장하고, 프로젝트에서 하드 링크를 통해 참조함.
- 하드 링크: 프로젝트의 node_modules/.pnpm 폴더 내에 패키지의 실제 파일을 하드 링크로 가져옴. 이는 중앙 저장소의 데이터를 복사하지 않고 그대로 참조.
- 심볼릭 링크: 프로젝트의 node_modules 폴더 내에서 의존성 관리를 위해 사용. 이 링크는 node_modules/.pnpm 폴더 내의 실제 패키지 위치를 가리킴.
동작 정리
- 패키지 다운로드 & 중앙 저장소 저장
~/.pnpm-store/ # 글로벌 저장소
v3/
files/
XX/ # 내용 기반 해시
XXHash... # 실제 파일
- 패키지를 처음 다운로드하면 글로벌 스토어에 저장
- 이때 하드링크를 사용해 디스크 공간 절약
프로젝트의 node_modules 구성
node_modules/
.pnpm/
foo@1.0.0/
node_modules/
foo/ # 하드링크: 글로벌 스토어의 파일들
- 글로벌 스토어의 파일들을 하드링크로 가져옴
- 각 패키지는 자신만의 격리된 공간을 가짐
의존성 패키지 연결
node_modules/
.pnpm/
foo@1.0.0/
node_modules/
foo/ # 하드링크
bar/ # 심볼릭 링크 → ../../bar@2.0.0/node_modules/bar
bar@2.0.0/
node_modules/
bar/ # 하드링크
- 패키지 간 의존성은 심볼릭 링크로 연결
- 이를 통해 의존성 그래프를 정확하게 표현
장점
- 디스크 공간 절약(콘텐츠 주소 지정 저장소에 패키지 중복 없이 설치 됨, 프로젝트 별로 저장하는게 아니라 중복된건 한 번만 설치함)
- 동일한 패키지가 다른 버전으로 각 프로젝트에서 사야되면 버전별로 콘텐츠 주소 저장소에 설치됨
- 예를 들어 A Project에서는 cookie@0.6.0이 사용되고, B Project에서는 cookie@0.5.0이 사용된다면 Content-addressable 스토어에 cookie@0.6.0와 cookie@0.5.0 두 개가 설치
- 설치 속도 향상
- 설명
- Dependency resolution: 필요한 모든 종속성을 스토어에 가져옵니다.
- Directory structure calculation: 종속성 기반으로 node_modules 디렉터리 구조가 계산됩니다.
- Linking dependencies: 스토어에서 node_modules로 하드 링크됩니다.
- 특징
- 이 세 단계를 async하게 진행해서 빠른거임
- 플랫하지 않은 node_modules
- 유령 종속성 문제 해결
- 설명