본문 바로가기
기타

npm, yarn, 그리고 yarn berry

by 유세지 2022. 8. 28.

yarn

2016년 10월에 출시된 yarnnpm과 같은 노드 패키지 매니저 도구 중 하나로, 당시 Facebook에서 근무하던 Sebastian McKenzie가 개발한 패키지 매니저입니다. 기존의 npm이 가지고 있던 문제점들을 해결하기 위해 등장했고, 성공적으로 안착하여 현재 수 많은 프로젝트에서 사용중인 오픈소스 프로젝트가 되었습니다.

 

이번 글에서는 yarnnpm의 어떤 문제들을 해결했는지, 그 이후엔 두 패키지 매니저가 어떻게 발전했고 yarn berry 가 등장하며 어떤 점이 달라졌는지까지 알아보겠습니다.

 

Node.js 패키지 매니저 도구 npm과 yarn

 

npm의 문제점

yarn이 등장하기 전, 과거의 npm에는 여러가지 문제가 있었습니다. 일관적이지 않은 패키지 버전, 고정되지 않은 설치 순서, 순차적인 설치로 인한 긴 소요 시간 등 사소한 불편에서부터 심각한 오류를 일으킬 수 있는 요소까지 다양한 문제들이 도사리고 있었습니다.

 

일관적이지 않은 패키지 버전

저를 포함하여 비교적 최근에 npm을 사용하신 분들이라면 느끼지 못하셨을 문제 중 하나입니다. 노드 모듈들은 기본적으로 시멘틱 버저닝이라는 기법을 사용하여 사용하는 모듈들의 버전을 나타낼 것을 권장하고 있습니다. 시멘틱 버저닝이란 간단히 말해 1.2.3 처럼 버전을 세 가지 숫자가 들어갈 수 있는 자리로 구분하고, 각각의 자리에 현재 버전이 이전 버전과 어떤 관계가 있는지 암시하도록 하는 방법입니다. 예를 들어 첫 번째 자리에 들어가는 숫자가 다르면, 두 버전은 이전 버전과 호환되지 않는 완전히 새로운, 메이저 버전이라는 의미입니다.

 

image

 

그러나 이 방식은 장점과 동시에 단점을 함께 가지고 있습니다. 위 사진을 보시면 모듈의 버전에 캐럿 기호(^)가 들어가 있는데, 이는 가장 앞 숫자인 메이저 버전을 제외한 두 자리 (마이너, 패치) 버전까지는 변경을 허용할 수 있다는 의미입니다. 현재 사진 속 바벨 코어의 버전이 ^7.17.9 이므로, 메이저 버전인 7이 변경되지 않는 범위에서는 모듈의 버전이 바뀌는 것을 허용합니다. 7.17.10 버전이나, 7.18.0 버전 등이 모두 사용될 수 있다고 볼 수 있겠네요.

 

이때 npm install 명령어를 통해 모듈들을 설치하면 해당 메이저 버전 중 최신 버전을 다운받게 됩니다. 빌드에 따라 유연하게 버전을 선택할 수 있다는 장점도 있지만, 이럴 경우 사용하는 모듈간의 버전 불일치로 인해 문제가 발생할 수도 있습니다. 서로 다른 환경에서 하나의 프로젝트를 개발 한다는건 어떤 문제가 발생할지 모르는 잠재적인 위험을 안고 가는 것과 같습니다.

 

고정되지 않은 설치 순서

말 그대로 개발자의 환경에 따라 모듈들의 설치 순서가 변경될 수 있습니다. 이미 만들어진 프로젝트에서 모듈을 추가로 설치하게 될 경우, 그 뒤에 npm i 명령어를 통해 처음부터 설치하는 사람과 모듈 설치 순서가 달라질 수 있습니다. 내가 어떤 순서로 모듈을 의존성에 추가했는지에 상관없이, npm은 모듈 이름을 사전 순서대로 정렬하여 순차적으로 설치하기 때문입니다.

이런 상황 역시 위에서 말했던 것 처럼 서로 다른 환경이 만들어지기 때문에 좋은 징조라고 보기 어렵습니다.

 

순차적인 설치로 인한 긴 소요 시간

npm은 모듈들을 한 번에 하나씩만 순차적으로 설치합니다. 설치해야하는 모듈이 많으면 많을수록, 총 설치 시간이 길어지게 됩니다. 첫 모듈 설치 시간이 길어지면 빌드 및 배포 시간에도 부정적인 영향을 끼치게 됩니다.

 

 

yarn의 주요 기능

yarn은 위의 문제들을 해결함과 동시에, 여러가지 기능들을 함께 탑재하여 등장하였습니다. 어떤 기능들을 가졌는지 살펴보겠습니다.

 

yarn.lock

yarn은 사용할 모듈의 버전을 지정하기 위해 프로젝트에 .lock 파일을 포함합니다. 정확한 버전을 지정하고 고정하기 때문에 다른 사용자가 프로젝트를 개발할때 항상 같은 버전의 모듈을 사용할 수 있도록 보장합니다. 이 파일 덕분에 기존 npm의 일관적이지 않은 패키지 버전 문제를 해결할 수 있었습니다.

 

물론 이 시점의 npm 메인테이너들도 이러한 문제를 인지하고 있었고, 패키지의 버전을 고정시키는 방법도 제시했었습니다. npm shrinkwrap 명령어를 사용하면 .lock 파일과 유사하게 패키지들의 버전을 포함한 npm-shrinkwrap.json파일을 생성해줍니다. 이 파일이 프로젝트에 함께 있을경우, package.json 에 명시된 버전을 무시하고 json 파일에 적힌 버전을 설치하게 됩니다.

 

하지만 npm의 경우 고정이 필요할때마다 매번 명령어를 입력하여 json 파일을 생성하는 과정이 필요했습니다. yarn은 이러한 버전 고정 파일을 자동으로 생성합니다.

 

checksum 사용

yarn은 패키지가 제대로 설치되었는지 확인하기 위해 checksum을 사용합니다. yarn.lock 파일을 확인해보면 resolved 주소 뒤에 해시값이 추가 되어있는 것을 확인할 수 있습니다. 이 해시값이 바로 checksum입니다.

 

물론 중간에 설치 요청을 가로채거나 원본을 수정하는등 공격자가 비집고 들어 올 확률은 낮지만, 제대로 설치되지 않은 경우를 대비해 패키지 파일의 무결성을 확인하는 안전장치가 추가된 것은 환영할만한 일입니다.

 

속도

yarn의 장점에서 빠질 수 없는 요소는 역시 속도입니다. yarn은 캐시를 사용하여 한 번 다운로드 한 패키지라면 그 다음부터는 엄청나게 빠른 속도로 설치할 수 있습니다. 심지어 인터넷이 연결되지 않은 환경에서도 설치가 가능합니다. 또한 병렬 다운로드를 지원하여 순차적으로 설치해야하는 npm과 달리 모듈들을 한꺼번에 설치해버립니다. 덕분에 설치할 패키지가 많을수록 npm과의 속도 차이는 점점 벌어집니다.

 

물론 빠른 속도가 yarn의 주된 목표는 아니었지만, 당시에는 상대적으로 훨씬 빠른 것도 사실이었기에 확실한 장점이었습니다. 이에 대한 yarn의 답변도 재미있습니다.

 

npm과 yarn의 현재

시간이 지나고, npm도 버전이 올라가면서 많은 부분이 개선되었습니다. 현재는 버전 고정을 위한 package-lock 파일도 자동으로 추가되고, 속도면에서도 yarn과 큰 차이가 없는 수준까지 따라왔습니다. node_modules 또한 이제 결정적(deterministic)인 트리 구조로 만들 수 있게 되었습니다.

 

yarn 또한 시간이 지나며 많이 발전하였고, 사용자가 많아지고 기능들이 추가되며 yarn 생태계 자체도 차츰 안정되어 갔습니다.

 

그러나 여러 발전을 거친 두 패키지 매니저 프로그램들에게도 여전히 남아있는 문제점들이 있었습니다.

 

유령 의존성

npm의 node_modules 폴더는 큰 용량을 자랑하기로 유명합니다. 아래 그림은 개발자 커뮤니티를 돌아다니다 보면 한 번쯤 보셨던 경험이 있으실 겁니다.

 

image

 

수 많은 패키지들과 그 패키지가 의존하는 모듈들을 전부 설치해버린다면 정말 블랙홀보다 무거운 node_modules 폴더가 탄생해버리겠죠. npm은 이 무거운 폴더를 경량화시키기 위해 호이스팅을 도입하고 있습니다.

 

용량을 줄이는 가장 간단한 방법은 중복을 제거하는 것입니다. npm과 yarn은 node_modules 내부의 중복된 패키지를 최소화하기 위해 각 패키지가 의존하고 있는 패키지들을 최상단으로 끌어올려 버립니다.

이렇게 되면 프로젝트가 의존하고 있는 패키지의 내부에 존재하던 각각의 node_modules에 있는 중복된 패키지가 최상단에 하나만 존재하게 되어 불필요한 중복을 제거할 수 있게 됩니다.

 

다만 이렇게 되면, 내가 설치한 패키지가 의존하고 있다는 이유로 그 패키지까지 슬쩍 불러올 수 있게 됩니다. 나는 설치한 적이 없지만, 그 패키지는 최상단에 존재 하고 있습니다. 마치 유령처럼요.

이러한 현상을 유령 의존성이라고 부릅니다.

 

이 외에도 비효율적인 설치와 의존성 검색 등은 여전히 npm과 yarn에게 숙제로 남아있었습니다. 그리고 2020년 1월, yarn은 이러한 숙제를 어느정도 해결한 v2를 공개했습니다.

 

yarn berry

yarn berry는 yarn v2 이상의 modern version yarn을 이르는 명칭입니다. 이에 따라 기존의 yarn v1은 yarn classic이라고 부르게 되었습니다.

 

Plug'n'Play

Plug'n'Play는 yarn berry가 제공하는 새로운 패키지 관리 시스템입니다.

기존의 무거웠던 node_modules 대신, 패키지들에 대한 정보는 .zip 파일로 압축하여 .yarn/cache 폴더에 저장하고 이를 찾기 위한 정보를 .pnp.cjs 파일에 기록합니다. 별도의 I/O 작업 없이도 패키지의 위치를 정확히 알 수 있기 때문에 시간도 단축되고, 중복 설치를 방지하며, node_modules를 만들고 패키지들을 호이스팅시킬 필요가 없는 Plug'n'Play의 특성 덕분에 유령 의존성 문제도 해결할 수 있었습니다. 패키지를 압축한 덕분에 줄어든 용량은 덤입니다.

 

Zero install

Plug'n'Play 전략으로 무거웠던 node_modules를 획기적으로 제거하고 옮긴 덕분에 이를 이용해 의존성까지 github에 올릴 수 있게 되었습니다.

 

github는 파일당 최대 용량을 500mb으로 제한하고, 원활한 이용을 위해 저장소당 1gb 미만의 크기를 유지 할 것을 권장하고 있습니다.

 

yarn berry를 통해 만든 의존성 폴더는 어지간히 크지 않은 이상 200mb를 넘지 않습니다. 덕분에 git clone 이후 별도의 설치가 필요 없이, 바로 사용할 수 있도록하는 zero-install을 시도해 볼 수 있습니다.

 

zero-install을 사용할 경우 로컬에서의 귀찮음도 줄어들지만, CI/CD 파이프라인을 구축한 경우 더 큰 효과를 볼 수 있습니다. 클론이 끝나자마자 곧바로 빌드가 가능해진 덕분에 배포까지 걸리는 속도가 대폭 단축됩니다.

 

마무리

오늘은 npm과 yarn, 그리고 yarn berry에 이르기까지 패키지 매니저가 가졌던 문제점들과 그 발전 과정을 살펴보았습니다. 이번 토이 프로젝트에 yarn berry를 새롭게 적용해 보려고 하다가 내친 김에 포스트까지 작성하게 되었네요.

 

틀린 내용에 대한 지적은 언제든 환영합니다. 긴 내용 읽어주셔서 감사합니다.

 

참고 문서

반응형

댓글