0biglife.

패키지 매니저로 CI 최적화하기(with. pnpm, yarn berry)

Productivity/CI/CD

· 2025-04-05

패키지 매니저로 CI 최적화하기(with. pnpm, yarn berry)

들어가며

CI/CD 파이프라인은 크게 다섯 단계로 이루어진다. 코드 푸시빌드, 테스트, 배포, 그리고 모니터링 단계로 구성되며, 특히 빌드 단계가 오래걸린다. npm install로 몇 분씩 잡아먹기도 하고, Typescript의 경우 JS로 트랜스파일된 후 번들링까지 거쳐야하므로 시간이 길어진다. 대부분의 CI/CD 환경은 Docker 기반이기 때문에 배포 때마다 설치와 빌드 과정을 반복하고, 협업 인원이 많을수록 비용은 n배수로 증가할지 모른다. 따라서, 이번 게시글은 반복적으로 발생하는 설치/빌드 작업을 효율적으로 운영할 수 있느냐는 곧 CI 최적화의 기본이자 핵심이 아닐까하는 생각으로 고민을 시작해본다.

어떻게 하면 설치와 빌드 속도를 줄일 수 있을까

당연하게도 해답은 설치와 빌드를 담당하는 툴에 있다. 기본적으로 Javascript 생태계는 모듈 시스템 기반으로 돌아가고, 우리는 패키지 매니저를 통해 외부 패키지를 내려받아서 각자 프로젝트 내부에서 개발을 한다.

패키지 매니저가 하는 일은 정리해보면 다음과 같다.

  • 의존성 관리 복잡성 해결 - 프로젝트 규모가 커질수록 의존하는 패키지 수와 복잡도가 즐가하기 때문에 이러한 수많은 의존성을 자동으로 해결하고 충돌을 방지한다.

  • 버전 일관성 유지 - 팀 내 모든 개발자가 동일한 패키지 버전을 사용하도록 보장되어 환경 차이로 인한 오류를 방지한다.

  • 개발 효율성 향상 - 간단한 명령어로 설치, 업데이트하여 개발 속도를 빠르게 한다.

그렇다면, 가장 기본적이면서 오래된 구식 패키지 매니저인 npm부터 알아보자. 패키지 매니저는 크게 네 가지로 나뉜다.

  1. NPM(Node Package Manager)
  2. Yarn Classic(v1)
  3. Pnpm(Performant NPM)
  4. Yarn Berry(v2+)

npm vs yarnnpm vs yarn

npm

2010년에 등장한 최초의 JS 패키지 매니저로, Node.js와 함께 배포된다. node_modules 폴더에 모든 의존성을 중첩으로 설치하여 node_modules에 보관시키기 때문에 기본적으로 무겁고, 패키지를 병렬처리 없이 하나하나 돌기 떄문에 특정 모듈을 탐색하는 과정도 느리다(좋게 말하면, 강력한 접근이지만 굉장히 비효율적인 것.!). 그리고 한 번씩 보았을 package.json 파일 내부에는 패키지 메타데이터를 기입하여 관리한다.

1{
2  "name": "0biglife-blog",
3  "version": "1.0.0",
4  "dependencies": {
5    "react": "^18.0.0",
6    "lodash": "^4.17.21"
7  }
8}

yarn classic

npm이 나오고 6년 후인 2016년에, npm의 성능과 보안 문제를 개선하고자 Facebook(현 Meta)과 Google 등의 협업으로 Yarn이 만들어지게 된다. 병렬 설치yarn.lock 파일을 통해 더 빠르고 안정적인 설치를 지원하며, 기존에 시멘틱 버저닝 방식에서 ^(캐럿)을 통해 서로 다른 환경에서 버전 일관성이 되지 않는 문제를 해결하고자 lock 파일을 따로 두어 모든 의존성 버전을 고정시켜 해당 문제를 개선시켰다. 하지만 여전히 node_modules 구조를 유지하기 때문에 구조적인 한계는 존재하고, 특히 유령 의존성 문제가 발생한다.


npm에도 package-lock.json이 있지 않나?

맞다🙂‍↕️ npm은 package-lock.json, yarn은 yarn.lock이라는 Lockfile로 의존성을 고정한다. 두 파일은 내부 동작의 차이가 있다. package-lock.json은 JSON 형태인 반면, yarn.lock은 YAML 형태로 읽기 쉬운 텍스트로 구성되어있고, package-lock.json은 설치 시점에 맞게 유연하게 결정되는 반면 yarn.lock은 적힌 버전을 그대로 설치한다. 따라서, yarn.lock은 더 엄격하게 의존성을 관리한다.

npm과 yarn classic의 단점

1. 여전히 너무나도 큰 node_modules

프로젝트 규모가 커짐에 따라 node_modules가 구조적으로 비대해지는 것은 불가피했고, 그렇기 때문에 파일 탐색 속도가 느려질 수 밖에 없다. node_modules 자체를 없애거나 새로운 구조로 관리할 순 없을까?

2. 유령 의존성

node_modules에는 package.json에 명시된 라이브러리들이 저장되는데, 각 라이브러리에도 독자적인 package.json이 존재하고, 이 package.json에 명시된 dependencies는 각각 해당 라이브러리가 저장된 디렉토리 하위에 node_modules를 또 만들어 저장한다.

여기서 문제가 발생한다🫢 복수의 라이브러리에서 동시에 같은 버전의 라이브러리를 dependency로 사용하고 있다면, 해당 라이브러리는 node_modules의 최상위 경로롤 끌어올리는데(여기서도 이를 호이스팅이라고 부른다), 이 때 간접 설치한 종속성에 개발자가 접근할 수 있게 되는 상황이 발생한다. 즉, 존재하지 않는 종속성에 의존하는 코드가 발생할 수 있다.

예를 들어, C 패키지가 A에 의존하고 A가 B를 사용하는 상황이라고 가정하자. 이 때, C는 B를 의존하지 않지만, A를 통해 B가 설치되어 있기 때문에 B를 import해도 오류없이 동작한다. 하지만 만약 A가 제거되거나 B를 더 이상 사용하지 않게 되면, C는 B가 필요하다고 착각하고 있던 의존성을 잃어버리는 문제가 발생하는 것이다.


pnpm

2017년에 출시된 pnpm은 Performant npm(효율적인 npm)라는 이름만 보아도 얼마나 획기적인 아이디어를 가지고 만들어졌는지 알 수 있는데, pnpm은 별도의 전역 저장소를 두고 의존성을 주소로서 참조하는 방식으로 node_modules 구조 자체를 바꿔버렸다. 이게 무슨 말일까? 자세히 살펴보자.

1. 콘텐트 주소 지정 저장소(Content-Addressable Storage)

pnpm은 동일한 패키지를 전역 저장소에 한 번만 저장하고, 프로젝트에서는 이 저장소를 참조하는 링크만 남기도록 동작시킨다. 패키지의 내용(content)을 해싱하여 저장 경로를 결정하기 때문에, 동일한 패키지가 중복 설치되지 않고 한 번만 저장된다. 예를 들어, 10개 프로젝트가 모두 lodash를 사용하더라도 실제로 디스크에 설치되는 lodash는 하나뿐이다.

즉, 위 사진처럼 동일한 패키지를 전역 저장소(Content-Addressable Storage)한 번만 저장하고 프로젝트(projcet_1, project_2)에서는 링크로서 참조하도록 한다.

2. 하드 링크와 심볼릭 링크의 조합

1# 전역 저장소에 저장된 react 패키지
2~/.pnpm-store/v3/files/
3└── react@18.0.0/
4    ├── index.js
5    └── package.json
6
7# 각 프로젝트에서 하드 링크로 참조
8my-app/node_modules/.pnpm/react@18.0.0/node_modules/react -> ~/.pnpm-store/react@18.0.0/

전역 저장소에서 실제 패키지를 하드 링크로 연결하여 디스크 사용량을 최소화시킨다. 즉, 프로젝트에 설치된 패키지는 복사본이 아니라, 전역 저장소에 존재하는 실체를 참조하는 방식이다. 이를 통해 설치 속도는 빨라지고 디스크 공간은 절약될 수밖에 없다. 헷갈리다면 하드 링크는 일종의 바로가기라고 보면 되겠다.

1my-app/
2├── node_modules/
3│   ├── react -> ./.pnpm/react@18.0.0/node_modules/react/    # 심볼릭 링크
4│   ├── lodash -> ./.pnpm/lodash@4.17.21/node_modules/lodash/    # 심볼릭 링크
5└── package.json

그렇다면 심볼릭 링크는 뭘까? 프로젝트 내에서 패키지 간 의존성 트리를 구성할 때, 이 심볼릭 링크(symlink) 를 사용한다. 아래와 같이 예를 들어보자.

1my-app
23├── node_modules
4│   ├── A  → .pnpm/A@1.0.0/node_modules/A       # 루트 의존성 A
56└── .pnpm
7    ├── A@1.0.0
8    │   └── node_modules/A
9    │       └── node_modules/B  → .pnpm/B@1.0.0/node_modules/B
1011    ├── B@1.0.0
12    │   └── node_modules/B
13    │       └── node_modules/C  → .pnpm/C@1.0.0/node_modules/C
1415    └── C@1.0.0
16        └── node_modules/C

재차 강조하자면, pnpm은 의존성 간 연결을 심볼릭 링크(synmlink)로 구성한다. 예를 들어 A → B → C 구조라면 위와 같이 디렉토리 구조가 만들어진다. 루프 프로젝트에는 A만 설치했지만, A는 B를, B는 C를 의존한다. 이 트리는 모두 .pnpm 내부에 심볼릭 링크로 구성되며 결국 node_modules 루트에는 A만 노출되기 때문에 명시적으로 설치하지 않은 C는 절대 import될 수 없기에 유령 의존성이 발생할 일 자체가 없어진다.

pnpm 정리

조금 복잡할 수 있어 정리해본다! pnpm은 기존 중첩된 node_modules 구조를 완전히 재정의한다. 전역 저장소에 하드 링크로 패키지를 보관하고, pnpm 디렉토리에서 심볼릭 링크로 의존성을 구성한 뒤, 최종적으로 필요한 구조만 node_modules에 노출시킨다. 그 덕분에 우리는 유령 의존성 문제를 방지하고, 디스크 공간도 절약하며, 설치 속도까지 빠르게 만드는 최적의 관리 환경을 구성할 수 있다.

yarn berry

pnpm면 충분하지 않나? 이미 완벽해보이는데 yarn berry는 대체 뭘까? pnpm은 실제로 이미 대규모 프로젝트나 모노레포 환경에서 많은 기업에서 사용 중이며 디스크 효율성과 의존성 충돌 방지에 뛰어난 성능은 실사례를 통해 많이 증명되었다.

다만, pnpm의 여전히 node_modules 디렉토리 기반이라는 점에서 파일 시스템에 의존하고 ,이로 인해 IDE 또는 일부 툴과의 통합에서 예외 처리가 필요하거나 큰 규모 프로젝트에서는 디스크 I/O로 인한 병목이 발생한다는 고려사항까지 생기기도 한다(달리말해, 일반적으로 구성되는 소/중규모 프로젝트에서는 pnpm으로 충분하다는 말이기도 하다).

따라서, 2020년에 발표된 yarn의 차세대 버전인 yarn berry는 이 고질적인 구조 문제인 node_modules 구조를 제거하는 것을 시작으로 의존성 탐색 자체를 가상화된 방식으로서 제시하며, 동시에 더 빠른 로딩이면서 더 낮은 I/O와 명확한 패키지 경로 해석까지 게오해주어 pnpm보다 뛰어난 성능까지도 제공한다(약간 유선 마우스 쓰다가 무선 마우스 쓰는 너낌이랄까).

아래 사진은 패키지 마니지별 성능 밴치마킹 자료인데, yarn berry의 어마무시한 성능을 확인할 수 있다. 동시에 npm을 왜 쓰지? 라는 의문까지 들기도 한다.

pnpm vs yarn berry with benchmark / githubpnpm vs yarn berry with benchmark / github

그렇다면 node_modules를 대체할 저장소는 어디 있는가?

1.yarn/cache/react-npm-18.0.0.zip # <- 이런 식

pnpm은 전역 저장소에 하드링크로 패키지를 저장하고 각 프로젝트에 링킹한다고 했다. yarn berry는 ./yarn/cache 안에 압축된 zip 형태로 저장시키고 그 파일을 압축 해제하지 않고 직접 읽어서 사용한다. 자세한건 아래에서 살펴보자.

1. Plug'n'Play(PnP)

yarn berry는 Plug'n'Play(PnP) 라는 새로운 방식으로 의존성을 로딩한다. node_modules를 아예 사용하지 않는 대신, 패키지 경로와 의존성 트리를 .pnp.cjs라는 하나의 파일에 정리하고, Node.js의 모듈 해석 알고리즘을 커스터마이징하여 직접 해당 경로에 패키지를 로드한다.

즉, 더 이상 Node.js가 node_modules를 탐색하지 않고, .pnp.cjs에 정해진 경로만 따라가기 때문에 모든 탐색 시간이 대폭 줄고, 유령 의존성 문제도 발생하지 않는다.

1// 예시: .pnp.cjs 일부 구조
2{
3  "packages": {
4    "lodash": {
5      "location": ".yarn/cache/lodash-npm-4.17.21.zip/node_modules/lodash",
6      "dependencies": {}
7    }
8  }
9}

Node.js의 require() 또는 import 구문이 실행되면, yarn은 내부적으로 .pnp.cjs에서 경로를 찾아 실제 위치의 압축 파일(zip) 안에 접근해 코드를 불러오게 된다.

yarn berry는 패키지를 .yarn/cache 아래에 .zip 파일 형태로 저장한다. 이 때, 이 zip 파일을 일종의 가상 파일 시스템(zipFS)처럼 처리해서 압축을 해제하지 않고도 내부 파일을 바로 읽어오는 것이 가능하다. 그렇기 때문에 디스크 I/O를 줄어들 수 있는 것!

2. 커스텀 Node.js 로더 사용

1번의 PnP 구조가 호라용되기 위해 yarn은 Node.js의 Custom ESM, require Loader를 통해 자체 해석기를 구현했다. (사실 이 부분은 꽤 깊은 기술적인 영역이기에 간단히만 공부해본 결과)기존 Node.js는 모듈을 불러올 때 디렉토리를 따라 node_modules를 탐색하는 방식인데, PnP에서는 node_modules가 아예 존재하지 ㅇ낳기 때문에 이 탐색 로직을 대체할게 필요하다.

그렇기 때문에 yarn은 자체 .pnp.cjs 파일을 기반으로 필요한 패키지의 정확한 경로를 지정해주는 해석기가 필요하여 자체 커스텀 Node.js 로더를 만들어 사용한다.

3. zip 파일 기반의 zipFS 사용

yarn berry는 패키지를 .yarn/cache 아래에 .zip 파일 형태로 저장한다. 이 때, 이 zip 파일을 일종의 가상 파일 시스템(zipFS)처럼 처리해서 압축을 해제하지 않고도 내부 파일을 바로 읽어오는 것이 가능하다. 그렇기 때문에 디스크 I/O를 줄어들 수 있는 것!

1.yarn/
2├── cache/
3│   └── lodash-npm-4.17.21.zip
4├── releases/
5├── plugins/
6└── .pnp.cjs

이 방식의 장점은

  1. 디스크 사용량 감소 - 압축 상태 유지
  2. I/O 횟수 감소 - 패키지를 모두 풀 필요가 없다
  3. 설치 속도와 런타임 접근 속도 향상

4. Zero-Install 전략

yarn berry는 ./yarn/cache에 모든 패키지를 .zip 형태로 저장한다고 했다. 이 때, .gitignore에는 이 파일을 제외시키게 되면, 누군가 프로젝트를 git clone하였을 때, .zip파일과 .pnp.cjs가 존재하기 때문에 별도 설치 과정 없이 바로 실행이 가능하다. 이것이 바로 "설치 없이 개발을 시작할 수 있는 환경"을 만드는 Zero-Install 전략이다.

1.yarn/*
2!.yarn/cache
3!.yarn/releases
4!.yarn/plugins
5.pnp.*

이건 예시 .gitignore 파일인데 !를 붙이면 "제외하지 말고, 강제로 포함해라"라는 의미로 업데이트된다.

5. 플러그인 시스템

yarn berry는 플로그인 아키텍처로 설계되었다고 한다. 기본 기능부터 workspace-tools, interactive-tools, plugin-typescript 등 별고 기능이 플로그인 형태로 제공되어 각 프로젝트에서 필요한 기능에 맞춰 확장 가능하다는 점도 장점이다.

현재 어떤 플러그인을 쓰고 있는지는 .yarnrc.yml에서 확인할 수 있다. yarn berry의 모든 설정은 이 파일을 통해 이루어진다.

1plugins:
2  - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
3    spec: "@yarnpkg/plugin-typescript"
4  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
5    spec: "@yarnpkg/plugin-interactive-tools"

path는 프로젝트 내 .yarn/plugins에 위치한 플러그인 파일을 가리키고, spec는 실제 패키지 이름을 가리킨다. 또는, yarn plugin list 명령어로 확인 가능하다.


패키지 매니저 동작 원리

여태 npm, pnpm, yarn classic, yarn berry까지 여러 패키지 매니저들을 어떠한 단점을 어떤 방식으로 보완하여 나오게 되었는지를 살펴보았다. 실제 예제로 npmyarn으로 마이그레이션하기 전에 패키지 매니저가 어떻게 동작하는지를 알아보고자 한다. CI/CD 환경에서 설치 속도가 느리거나 유령 의존성 문제나 디버깅 지옥에 빠졌을 때를 대비하여 동작 흐름을 이해하는 것은 분명 도움이 되리라!

모든 패키지 매니저는 세 가지 단계(Resolution → Fetch → Link)를 수행한다. 예를 들어, yarn install를 실행하면 단계별 진행상황을 모니터링하게 된다.

10biglife-blog git:(main)yarn install
2➤ YN0000: · Yarn 4.8.1
3➤ YN0000: ┌ Resolution step  # ← 여기
4➤ YN0000: └ Completed
5➤ YN0000: ┌ Post-resolution validation
6➤ YN0086: │ Some peer dependencies are incorrectly met by dependencies; run yarn explain peer-requirements for details.
7➤ YN0000: └ Completed
8➤ YN0000: ┌ Fetch step  # ← 여기
9➤ YN0000: └ Completed in 0s 326ms
10➤ YN0000: ┌ Link step   # ← 여기
11➤ YN0000: │ ESM support for PnP uses the experimental loader API and is therefore experimental
12➤ YN0000: └ Completed
13➤ YN0000: · Done with warnings in 0s 424ms

1. Resolution 단계

패키지의 정확한 버전을 결정하고 호이스팅을 통해 의존성 트리를 구축한다. 실제 패키지를 재귀적으로 탐색하면서 어떤 패키지가 필요한지 판별하고 버전을 결정하며 lock 파일(package-lock.json, yarn.lock 등)을 생성시키며 단계를 마친다. 그러기 때문에 이 단계가 가장 시간이 오래 걸린다.

2. Fetch 단계

결정된 버전의 패키지를 실제로 다운로드하는 과정이다. npm 레지스트리나 프라이빗 레지스트리에서 패키지를 가져오고, 다운로드한 패키지를 로컬에 저장하여 이후 설치 시 재사용한다.

다운로드한 패키지를 프로젝트에서 사용할 수 있도록 연결한다. 이 단계에서 패키지 매니저마다 다른 방식을 사용하기 때문이 성능 차이가 발생한다.

  • npm: node_modules 안에 실제 패키지를 복사하여 저장하고, 각 패키지의 의존성은 해당 패키지의 node_modeuls에 중첩되어 저장된다. 디스크 사용량이 큰건 이미 위에서 설명했기에 생략한다.
1node_modules
2   ├── lodash
3   │   ├── index.js
4   │   └── node_modules
5   │       └── ...
6   └── react
7       ├── index.js
8       └── node_modules
9           └── ...
  • pnpm: 하드 링크와 심볼릭 링크가 여기서 나온다. 두 링크를 사용하여 패키지 정보를 생성한다. 전역 저장소에 패키지를 저장하고, 프로젝트의 node_modules에는 심볼릭 링크를 생성한다. 패키지 간 의존성도 심볼릭 링크를 통해 연결시킨다.
1my-app/
2   ├── node_modules/
3   │   ├── react -> ~/.pnpm-store/react@18.0.0/
4   │   └── lodash -> ~/.pnpm-store/lodash@4.17.21/
5   └── package.json

아래 사진처럼 node_modules를 열고 폴더 우측 화살표 아이콘이 있으면 링킹이 되었다는걸 의미하고, 커서를 대면 심볼릭 링크를 확인해볼 수 있다.

  • yarn pnp: node_modules를 생성하지 않고, .pnp.cjs 파일에 패키지의 위치와 버전 의존성 정보를 저장하고, 이 파일을 로드해서 직접 커스터마이징된 로더를 사용하여 패키지를 로드한다. 패키지를 압축하여 저장하고, zipFS를 사용하여 압축을 해제하지 않고 바로 읽어온다. 이 때, react 같은 패키지를 객체의 id값을 참조하듯이 경로를 가져오는 방식을 인터페이스 링크 방식이라고 한다.
1// .pnp.cjs 내용 일부
2{
3  "dependencies": {
4    "react": {
5      "version": "18.0.0",
6      "path": ".yarn/cache/react-npm-18.0.0.zip/node_modules/react/"
7    },
8    "lodash": {
9      "version": "4.17.21",
10      "path": ".yarn/cache/lodash-npm-4.17.21.zip/node_modules/lodash/"
11    }
12  }
13}

패키지 매니저 속도 비교

이제 빠르게 npm, pnpm, yarn berry의 패키지 설치 속도를 비교해보자.

npm 설치

1# yarn 관련 파일 삭제
2rm -rf .yarn .pnp.* .yarnrc.yml
3rm yarn.lock
4
5# npm 설치
6time npm install

현재 yarn berry인 환경을 npm으로 복원부터 해본다. yarn 관련 파일을 지우는 명령어부터 입력하고 npm install 앞에 time을 붙여 측정한다.

1➜  0biglife-blog git:(main)time npm install
2
3added 651 packages, and audited 652 packages in 1m
4
5282 packages are looking for funding
6  run `npm fund` for details
7
8npm install  12.80s user 7.55s system 32% cpu 1:02.02 total
  • 위 지표는 실제 기다린 시간은 1분 2초이고 CPU는 약 20.35초(12.80 + 7.55) 동안 바쁘게 일했으며 CPU 사용량은 32% 인 것을 의미한다. 20초 외 시간 동안은 디스크I/O, 네트워크 대기, 싱글 스레드 병목 등 이유로 더 걸렸을 것이다.

pnpm 설치

pnpm 설치도 동일하게 npm 관련 파일을 지우고 실행한다. 추가적으로 package.jsonpackageManager도 필요시 수정해줘야한다.

1rm -rf node_modules package-lock.json
2
3time pnpm install

측정 결과는 다음과 같다.

1➜  0biglife-blog git:(main)time pnpm install
2
3Downloading typescript@5.3.3: 5.76 MB/5.76 MB, done
4Downloading next@15.2.4: 23.87 MB/23.87 MB, done
5Downloading @next/swc-darwin-arm64@15.2.4: 41.23 MB/41.23 MB, done
6Packages: +644
7+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8Progress: resolved 687, reused 616, downloaded 33, added 644, done
9
10dependencies:
11+ @chakra-ui/color-mode 2.2.0
12+ ...(생략)
13+ webpack-bundle-analyzer 4.10.2
14
15devDependencies:
16+ @eslint/eslintrc 3.3.1
17+ ...(생략)
18+ typescript 5.3.3 (5.8.3 is available)
19
20Done in 15.7s using pnpm v10.7.1
21pnpm install  6.92s user 6.96s system 73% cpu 18.792 total
  • pnpm 설정 결과, 실제 기다린 시간은 18.8초이고 npm 대비 CPU를 더 적극적으로 활용해서 처리 속도를 높인 것으로 보여 약 3배 가까이 빨라졌다.

yarn berry 설치

동일하게, 클린업부터 해주고 설치한다. 이번에는 추가적으로 yarn berry 설정을 위해 corepack enableyarn set version berry를 해줘야한다.

1# 클린업
2rm -rf node_modules pnpm-lock.yaml package-lock.json
3
4# yarn berry 설정
5corepack enable
6yarn set version berry
7
8time yarn install

그 결과 역시 다음과 같다.

1➜  0biglife-blog git:(main)time yarn install
2
3➤ YN0000: · Yarn 4.8.1
4➤ YN0000: ┌ Resolution step
5➤ YN0085: │ + @chakra-ui/color-mode@npm:2.2.0, @chakra-ui/icons@npm:2.2.6, and 685 more.
6➤ YN0000: └ Completed in 3s 104ms
7➤ YN0000: ┌ Post-resolution validation
8➤ YN0086: │ ...(생략)
9➤ YN0000: └ Completed
10➤ YN0000: ┌ Fetch step
11➤ YN0013: │ 18 packages were added to the project (+ 20.67 MiB).
12➤ YN0000: └ Completed in 0s 529ms
13➤ YN0000: ┌ Link step
14➤ YN0000: │ ...(생략)
15➤ YN0000: └ Completed in 2s 959ms
16➤ YN0000: · Done with warnings in 6s 659ms
17yarn install  4.96s user 1.15s system 81% cpu 7.500 total
  • yarn berry의 경우, 실제 기다린 시간은 7.5초이고 약 18초 걸린 pnpm보다도 50% 이상 빨라진 7.5 성능을 보여준다. 규모가 크고 더 무거운 프로젝트일수록 성능은 크게 차이날테니 이미 충분히 증명되었다고 봐도 무방하다.

마치며

이것으로 현존하는 패키지 매니저에 대한 동작 원리와 성능 비교를 해보았다. npm, pnpm, yarn berry까지 각각의 장단점과 성능을 비교해보았고, 특히 yarn berry는 node_modules를 아예 사용하지 않고 가상화된 방식으로 의존성을 관리하는 혁신적인 접근을 보여주었다. 이로 인해 패키지 설치 속도와 디스크 I/O를 대폭 줄일 수 있었다.

생산성을 높이기 위한 고민은 어쩌면 비즈니스 임팩트가 있는 액션일지도 모른다. 그리고 이런 기술에 대해 깊게 알고 있어야 차세대 기술이 새로 나왔을 때 어느 부분이 업데이트 되었는지 빠르게 알고 적용해볼 수도 있다.

추가적으로, yarn berry 자체로 훌륭하지만, 굳이 좀 더 보완을 해볼 수 있다면 : 일부 실사용 환경에서 발생하는 불편함, 또는 기존 npm, pnpm에서의 장점인 호환성을 확보하기 nodeLinker: pnpm 옵션을 통해 설정할 수도 있을 것 같다. 그렇게 된다면, yarn의 빠른 설치 속도와 zip 캐시는 물론, pnpm의 하드링크와 심볼릭 링크를 통한 디스크 사용량 절감까지 모두 누릴 수 있을 것이다. (물론, 이 경우에는 yarn berry의 PnP 기능은 비활성화된다) 적용기에 대한건 별개 게시글에서 더 다뤄보도록 한다.

Index

들어가며어떻게 하면 설치와 빌드 속도를 줄일 수 있을까npmyarn classicpnpmyarn berry패키지 매니저 동작 원리패키지 매니저 속도 비교npm 설치pnpm 설치yarn berry 설치마치며