
npm -> Yarn BerryYarn Berry로 변경하였습니다. 이번 시간에는 Yarn Berry에 대해 알아보면서 Yarn Berry를 사용하면서 느낀 장단점을 공유하고자 합니다.Yarn Berry의 사용방법을 정리한 글이 아닌 Yarn Berry를 사용하면서 느낀 장단점을 공유하는 글입니다.Yarn Berry를 사용하실 계획이라면 한 번쯤 읽어 보시는 것을 추천드립니다.Yarn Berry란 2020년 1월에 출시된 Yarn Classic의 업그레이드 버전입니다. yarn 팀에서는 본질먹인 새로운 코드 베이스와 새로운 원칙을 가진 완전리 새로운 패키지 매니저라는 것을 분명하게 하기 위해 Yarn Berry라고 이름을 붙였습니다.Yarn Berry나 yarn classic, npm, pnpm 등 모든 패키지 매니저의 역할은 동일합니다. 매키지 매니저의 역할은 다음과 같습니다.npm의 문제점Yarn Berry를 사용했을까요?npm의 문제점을 알아보겠습니다.
미처버린 node_modules...
npm이 관리하는 node_modules는 비효율적이고 무겁습니다.npm의 비효율적인 의존성 탐색에 있습니다.npm은 의존성을 탐색할 때, node_modules를 탐색하고, 없으면 상위 디렉토리로 올라가서 다시 node_modules를 탐색합니다.node_modules를 찾지 못하고 프로젝트의 루트 디렉토리까지 올라가게 됩니다.npm은 중복해서 설치되는 node_modules를 아끼기 위해 호이스팅 기법을 사용합니다.
중복 모듈 A(1.0)과 B(1.0)을 root node_modules에 호출
B(1.0)이 root node_modules에 설치됩니다.B(1.0)은 require 할 수 없어야 하지만 의존성 탐색을 통해 설치되지 않은 B(1.0)을 root node_modules에서 찾아서 사용할 수 있습니다.저또한 lodash 라이브러리를 설치하지 않고 사용한 경험이 있습니다.
이는 `npm`의 호이스팅 기법으로 인해 발생한 문제였습니다.PnP (plug n play)Yarn Berry는 위에 언급한 문제를 해결하기 위해 PnP라는 새로운 패러다임을 도입했습니다.PnP는 node_modules를 사용하지 않고, 패키지의 종속성을 프로젝트 단일 디렉토리 트리(.yarn/cache)에 직접 설치합니다..pnp.cjs 파일에 저장되며 이를 통해 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디에 위치하는지 바로 알 수 있습니다.react@18.2.0을 설치한다면 .pnp.cjs 파일에 다음과 같이 저장됩니다.1 // react 패키지 중에서
2 ["react", [
3 // npm:18.2.0 버전을 설치하고
4 ["npm:18.2.0", {
5 // 해당 위치에 존재하며
6 "packageLocation": "./.yarn/cache/react-npm-18.2.0-1eae08fee2-88e38092da.zip/node_modules/react/",
7 // 해당 패키지의 의존성은 다음과 같다.
8 "packageDependencies": [
9 ["react", "npm:18.2.0"],
10 ["loose-envify", "npm:1.4.0"]
11 ],
12 "linkType": "HARD"
13 }]
14 ]],PnP 시스템에서 각 의존성은 Zip 아카이브로 관리됩니다.
React@18.2.0을 설치하면 다음과 같은 압축파일로 관리됩니다.1.yarn/cache/react-npm-18.2.0-1eae08fee2-88e38092da.zip.pnp.cjs 파일이 지정하는 바에 따라 동적으로 Zip 아카이브의 내용이 참조됩니다.PnP는 다음과 같은 장점을 가집니다.node_modules를 사용하지 않기 때문에 설치가 빠르게 완료됩니다.Yarn Berry를 도입하면서 얼마나 성능이 향상되었는지 npm과 비교해보겠습니다.node_modules vs .yarnnpm을 사용할 때는 node_modules를 사용하기 때문에 자연스럽게 프로젝트의 용량이 커졌습니다.node_modules의 용량은 다음과 같습니다.1# client
2$ du -sh node_modules
3614M node_modules
4
5# server
6$ du -sh node_modules
7465M node_modulesYarn Berry를 사용하면서 생성된 .yarn 디렉토리의 용량을 확인해 보겠습니다.1# client
2$ du -sh node_modules
3324M .yarn
4
5# server
6$ du -sh node_modules
768M .yarn| 분류 | npm | Yarn Berry | 증감(%) |
|---|---|---|---|
| 클라이언트 | 614MB | 324MB | 약 48% |
| 서버 | 465MB | 68MB | 약 86% |
npm을 사용할 때와 Yarn Berry를 사용할 때의 docker image 빌드 시간/용량을 비교해 보겠습니다.npm을 사용할 때의 docker image 빌드 시간은 다음과 같습니다.1# client
2$ docker build --no-cache -t prev_front .
3[+] Building 191.8s (12/12) FINISHED
4
5# server
6$ docker build --no-cache -t prev_back .
7[+] Building 88.8s (12/12) FINISHEDYarn Berry를 사용할 때의 docker image 빌드 시간을 확인해 보겠습니다.1# client
2$ docker build --no-cache -t front .
3[+] Building 132.1s (22/22) FINISHED
4
5# server
6$ docker build --no-cache -t back .
7[+] Building 32.2s (26/26) FINISHEDnpm을 사용할 때의 docker image 용량은 다음과 같습니다.1# client
2$ docker images -f "reference=prev_front"
3REPOSITORY TAG IMAGE ID CREATED SIZE
4prev_front latest 40b0156a361d About a minute ago 742MB
5
6# server
7$ docker images -f "reference=prev_back"
8REPOSITORY TAG IMAGE ID CREATED SIZE
9prev_back latest 48dae3e2081c About a minute ago 453MBYarn Berry를 사용할 때의 docker image 용량을 확인해 보겠습니다.1# client
2$ docker images -f "reference=front"
3REPOSITORY TAG IMAGE ID CREATED SIZE
4front latest 43ef86c4c320 About a minute ago 591MB
5
6# server
7$ docker images -f "reference=back"
8REPOSITORY TAG IMAGE ID CREATED SIZE
9back latest cff45f3475a6 About a minute ago 188MBnpm을 사용할 때의 docker image 용량에 비해 상당히 줄어든 것을 확인할 수 있습니다.| 분류 | npm | Yarn Berry | 증감(%) |
|---|---|---|---|
| 클라이언트 | 742MB (191.8s) | 591MB (132.1s) | 약 20% |
| 서버 | 453MB (88.8s) | 188MB (32.2s) | 약 58% |
github action을 통해 CI/CD를 진행할 때의 빌드 시간을 비교해 보겠습니다.npm을 사용할 때의 빌드 시간은 다음과 같습니다.Dockerfile을 제외하고 동일하기 때문에 jobs의 build step만 비교하겠습니다.deploy.yml 파일이 궁금하신 분들은 다음 링크를 참고해주세요.| steps | npm | Yarn Berry | 증감(%) |
|---|---|---|---|
| Install the project dependencies | 43s | 55s | 약 127% |
| Docker build & push to push | 6m 40s | 3m 26s | 약 48% |
Yarn Berry가 약 27% 더 느렸습니다.| steps | npm | Yarn Berry | 증감(%) |
|---|---|---|---|
| Install the project dependencies | 19s | 6s | 약 68% |
| Docker build & push to push | 1m 47s | 1m 11s | 약 33% |
| 분류 | npm | Yarn Berry | 증감(%) |
|---|---|---|---|
| 클라이언트 | 8m 19s | 5m 44s | 약 31% |
| 서버 | 3m 4s | 2m 4s | 약 32% |
Yarn Berry는 무조건 좋을까요? 글쎄요... 다음으론 제가 사용하면서 느낀 단점을 정리해보겠습니다.Yarn Berry를 사용한다면 초기 설정을 진행해야 합니다. 이때 초기에 많이 해매는 부분이 있었는데 바로 typescript sdk, eslint, prettier 설정이었습니다.Yarn Berry를 우아한테크캠프를 진행하면서 알게 되었습니다. 일주일이라는 짧은 시간안에 해당 기술을 녹여내야했기 때문에 초기설정에 많은 시간을 할애할 수 없었습니다.Yarn Berry는 초기 설정해야되는 부분이 많았고 vscode를 사용한다면 typescript sdk 설정을 진행해야하는 점, sdk 설정이전 eslint와 prettier를 install하고 sdk를 설정해야했으며 이를 위해 각 워크스페이스의 typescript, eslint, prettier 버전을 맞춰줘야 했습니다.Yarn Berry를 처음 사용한다면 겪을 수 있는 문제라고 생갹하여 단점으로 정리했습니다. 만약 Yarn Berry를 사용하신다면 사용 이전에 충분히 설치 방법, 동작 등을 숙지하시고 진행하는 것을 추천드립니다..git이 무거워요...Yarn Berry의 단점을 설명하는데 뜬금없이 .git에 대한 얘기가 나왔습니다. 해당 부분은 아래 이미지를 확인하시면 이해가 되실 것 같습니다.
왼쪽: 이전 프로젝트 / 오른쪽: 현재 프로젝트
.git 디렉토리보다 현재 프로젝트의 .git 디렉토리가 무거워진 것을 확인할 수 있습니다. (심지어 왼쪽은 client, server의 커밋 내역까지 포함되어 있습니다.).yarn/cache 디렉토리에 대한 변경사항이 커밋내역으로 존재하기 때문에 발생하는 문제입니다.Yarn Berry를 사용하는 궁극적인 이유는 성능적인 이점을 가져가기 위함이여서 이러한 단점을 크게 신경쓰지 않았지만 의존성 버전 업데이트 등 Dependency Tree에 변경사항이 생길때마다 change 파일의 개수가 급격하게 증가했습니다.eslint, prettier가 적용되는 과정에서 추가적인 시간이 소요되는 등의 문제가 발생하기도 했습니다.
.yarn/cache 삭제시 1583개의 file change가 발생합니다
Yarn Berry는 .yarn/cache 디렉토리에 의존성 정보를 담고 있습니다. github repository를 clone할 경우 이미 .yarn/cache 디렉토리에 의존성 정보가 존재하기 때문에 yarn install을 진행하지 않아도 프로젝트를 실행할 수 있다고 설명했습니다.Pnp를 지원하지 않거나 현재 환경에 대한 바이너리 정보를 가지고 동작해야하는 Dependency들(파일 쓰기, 스크립트 실행 등)은 install시 .yarn/cache 디렉토리에 zip파일 형태로 저장되는 것이 아닌 .yarn/unplugged 디렉토리에 별도의 바이너리 파일로 관리됩니다..yarnrc.yml 파일에 enableScripts: false로 설정해주는 방법입니다. (물론 PnP 자체를 지원하지 않는 라이브러리들은 .yarn/unplugged에 생성됩니다.)github action에서의 성능 비교 부분에서 client 측 install time이 이전 프로젝트보다 현재 프로젝트가 약 27% 더 느렸습니다. 이는 github 상에 존재하지 않는 .yarn/unplugged 패키지 들을 install 하기 위함이었습니다.
겨우 3개의 파일을 설치하는데 46초라는 시간이 걸렸네요..
.yarn/unplugged가 필요 없어지는 경우도 있고 현재 저의 블로그 또한 .yarn/unplugged가 없더라도 정상적으로 동작합니다. 하지만 sharp 라이브러리를 통해 이미지를 최적화하기 위해서 .yarn/unplugged를 사용하였습니다.1$ du -hs unplugged
2241.7M unplugged.yarn/unplugged 디렉토리 용량입니다. 이는 현재 프로젝트의 .yarn/cache 디렉토리 용량의 약 50%에 해당합니다. 프로젝트가 리눅스 환경에서 동작하보니 .yarn/unplugged 디렉토리의 용량이 굉장히 크게 증가한 모습입니다. (local에서 생성된 디렉토리보다 약 50% 정도 증가했습니다.)npm을 사용할 때는 클라이언트, 서버를 빌드할 경우 해당 빌드파일을 그대로 사용 가능했습니다.tsc를 통해 빌드된 js 파일을 실행하기만 하면 되었습니다.Yarn Berry를 사용하면서 클라이언트, 서버를 빌드할 경우 .pnp.cjs에 저장된 의존성 트리를 통해 라이브러리를 참조하기 때문에 .yarn/cache 디렉토리 등 추가적인 파일들을 COPY 함으로써 이미지 용량이 증가하였습니다.npm을 사용하여 docker image를 재생성한다면 image size를 절반 이하로 줄일 자신이 있기 때문에 Yarn Berry를 사용한 image size와 관련해선 크게 이점을 가졌다고 생각하지 않습니다. (서버 측은 제외입니다.)Yarn Berry를 도입하면서 성능적인 이점을 굉장히 많이 봤다고 생각합니다. Zero Install을 통해 가장 중점적으로 생각했던 build time을 크게 줄일 수 있었었고 새로운 기술을 도입하면서 개발 능력 또한 향상시킬 수 있었습니다. (서버 측이 굉장히 큰 성능적 이점을 가질 수 있었습니다.)Yarn Berry는 Zero Install을 제외하고도 굉장히 많은 기능이 존재하며 효율적으로 프로젝트를 관리할 수 있습니다. Yarn에서 제공하는 플러그인, workspace 등의 수많은 기능들을 통해 앞으로 개발에 있어 밑바탕이 될 것이라고 생각합니다.Yarn Berry에 대한 사용후기는 여기까지 하겠습니다. 감사합니다.