패키지 매니저로 CI 최적화하기(with. pnpm, yarn berry)
Productivity/CI/CD
· 2025-04-05

들어가며
CI/CD 파이프라인은 크게 다섯 단계로 이루어진다. 코드 푸시
와 빌드
, 테스트
, 배포
, 그리고 모니터링
단계로 구성되며, 특히 빌드 단계가 오래걸린다. npm install
로 몇 분씩 잡아먹기도 하고, Typescript의 경우 JS로 트랜스파일된 후 번들링까지 거쳐야하므로 시간이 길어진다. 대부분의 CI/CD 환경은 Docker 기반이기 때문에 배포 때마다 설치와 빌드 과정을 반복하고, 협업 인원이 많을수록 비용은 n배수로 증가할지 모른다. 따라서, 이번 게시글은 반복적으로 발생하는 설치/빌드 작업을 효율적으로 운영할 수 있느냐는 곧 CI 최적화의 기본이자 핵심이 아닐까하는 생각으로 고민을 시작해본다.
어떻게 하면 설치와 빌드 속도를 줄일 수 있을까
당연하게도 해답은 설치와 빌드를 담당하는 툴에 있다. 기본적으로 Javascript 생태계는 모듈 시스템 기반으로 돌아가고, 우리는 패키지 매니저를 통해 외부 패키지를 내려받아서 각자 프로젝트 내부에서 개발을 한다.
패키지 매니저가 하는 일은 정리해보면 다음과 같다.
-
의존성 관리 복잡성 해결 - 프로젝트 규모가 커질수록 의존하는 패키지 수와 복잡도가 즐가하기 때문에 이러한 수많은 의존성을 자동으로 해결하고 충돌을 방지한다.
-
버전 일관성 유지 - 팀 내 모든 개발자가 동일한 패키지 버전을 사용하도록 보장되어 환경 차이로 인한 오류를 방지한다.
-
개발 효율성 향상 - 간단한 명령어로 설치, 업데이트하여 개발 속도를 빠르게 한다.
그렇다면, 가장 기본적이면서 오래된 구식 패키지 매니저인 npm부터 알아보자. 패키지 매니저는 크게 네 가지로 나뉜다.
- NPM(Node Package Manager)
- Yarn Classic(v1)
- Pnpm(Performant NPM)
- Yarn Berry(v2+)
npm 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 2│ 3├── node_modules 4│ ├── A → .pnpm/A@1.0.0/node_modules/A # 루트 의존성 A 5│ 6└── .pnpm 7 ├── A@1.0.0 8 │ └── node_modules/A 9 │ └── node_modules/B → .pnpm/B@1.0.0/node_modules/B 10 │ 11 ├── B@1.0.0 12 │ └── node_modules/B 13 │ └── node_modules/C → .pnpm/C@1.0.0/node_modules/C 14 │ 15 └── 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 / 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
이 방식의 장점은
- 디스크 사용량 감소 - 압축 상태 유지
- I/O 횟수 감소 - 패키지를 모두 풀 필요가 없다
- 설치 속도와 런타임 접근 속도 향상
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
까지 여러 패키지 매니저들을 어떠한 단점을 어떤 방식으로 보완하여 나오게 되었는지를 살펴보았다. 실제 예제로 npm
을 yarn
으로 마이그레이션하기 전에 패키지 매니저가 어떻게 동작하는지를 알아보고자 한다. 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 레지스트리나 프라이빗 레지스트리에서 패키지를 가져오고, 다운로드한 패키지를 로컬에 저장하여 이후 설치 시 재사용한다.
3. Link 단계
다운로드한 패키지를 프로젝트에서 사용할 수 있도록 연결한다. 이 단계에서 패키지 매니저마다 다른 방식을 사용하기 때문이 성능 차이가 발생한다.
- 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.json
의 packageManager
도 필요시 수정해줘야한다.
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 enable
과 yarn 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 기능은 비활성화된다) 적용기에 대한건 별개 게시글에서 더 다뤄보도록 한다.