Yarn berry 도입기

동동·2021년 9월 24일
14
post-thumbnail

Node.js 모듈 시스템 이해

CommonJS는 Node.js의 첫 번째 내장 모듈 시스템입니다.

  • commonJS에서는 로컬 파일 시스템으로부터 모듈을 임포트하기 위해 require.resolve 알고리즘에 의해 node_modules를 순회하면서 모듈의 위치를 찾아냅니다.
  • 현 위치의 node_modules에 모듈이 존재하지 않으면 상위 디렉토리의 node_modules 디렉토리에서 모듈을 찾습니다.
  • 이러한 require.resolve 알고리즘은 많은 I/O call을 낳기 때문에 런타임에서 node 구동시에 시간이 오래 걸리는 원인이 되기도 합니다.

기존의 package manager는 node_modules에 모듈을 설치합니다.

  • node_modules 디렉토리를 생성하는 것은 I/O-heavy operation 이기 때문에 package manager가 딱히 최적화할 방법이 없습니다.
  • package manager가 설치하려고 하는 모듈이 이미 설치되어 있더라도 비효율적인건 마찬가지입니다. 왜냐하면, package manager는 node_modules 디렉토리를 일일이 순회하면서 현재 설치된 파일의 버젼을 확인하고 비교하기 때문입니다.

이를 해결하기 위한 방법: Plug'n'Play

package manager는 이미 디스크에 모듈을 설치하였기 때문에 모듈 의존성 트리에 대해서 이미 알고 있습니다.

  • 그렇다면 모듈의 위치를 찾는 것이 왜 Node.js 의 책임이어야 할까요?
  • '디스크상 모듈의 위치에 대해 인터프리터에게 알려주는 것'과 '여러 버젼의 모듈 간 의존성을 관리하는 것'은 package manager의 일이어야 합니다.

node_modules 가 아닌 .pnp.cjs

  • yarn berry(yarn 2.0 이상 버젼)는 모듈 설치시 node_modules 디렉토리가 아닌 .pnp.cjs 파일을 생성합니다.
  • .pnp.cjs 파일에는 모듈의 버젼과 저장된 위치, 참조하고 있는 다른 모듈들을 참조 테이블에 모조리 기록해둡니다.

Plug'n'Play의 이점은 다음과 같습니다

  • .pnp.js 파일을 찾는 것만으로 node에게 어떤 패키지가 어디에 있는지 바로 말해줄 수 있습니다.
  • 새로운 의존성 설치시에도 node_modules를 보고 현재 설치된 파일의 버젼을 확인할 필요가 없습니다.
  • .pnp.js 와 .yarn 디렉토리를 레포지토리에 추가한다면, 의존성을 설치할 필요가 없다. 레포지토리를 클론해서 바로 쓰면 됩니다. → zero install 전략

유의사항

  • package.json 의 scripts를 실행시키는 것이 아니라 직접 node 를 구동시킨다면, yarn node를 실행하여야 합니다. scripts 는 .pnp.cjs 를 runtime dependency 로 등록하지만, 그냥 node 는 그렇지 않습니다. yarn node 로 실행시켜 .pnp.cjs 를 runtime dependency 로 추가하여야 합니다.
  • yarn node 가 하는 것은 NODE_OPTIONS 환경 변수에 .pnp.cjs 경로가 포함된 —require 옵션을 추가하는 것입니다.
    NODE_OPTIONS="--require $(pwd)/.pnp.cjs" node ./server.js

zero install 전략

  • 모든 것을 복잡하게 만드는 package manager의 역할을 제거해버려 가능한 프로젝트를 빠르고 안전하게 하자는 철학입니다.
  • package manager의 역할은 다음과 같습니다.
    • 디스크상 모듈의 위치에 대해 인터프리터에게 알려주는 것
    • 여러 버젼의 모듈 간 의존성을 관리하는 것

왜 zero install 철학이 대두되었나요?

  • 추후에 yarn 의 버젼이 바뀌어서 모듈을 설치하는데 버그를 낳을 수도 있습니다.
  • production 환경이 변경되어서 yarn install 이 임시 디렉토리에 더이상 파일을 작성할 수 없을 수도 있습니다.
  • 네트워크가 실패하거나, 자격 증명(credentials)가 바뀌어서 인증 오류에 직면할 수도 있습니다.

이러한 이슈를 피하는 유일한 방법은 가능한 한 package manager를 조금 사용하는 것입니다.

어떻게 적용할 수 있나요?

  • .yarn/cache 디렉토리를 remote repository에 추가합니다.
    • yarn berry는 모듈 설치시 binary 파일로 압축하여 .yarn/cache 에 저장합니다. 다음번에 yarn install 을 실행시킬 경우 .yarn/cache 에 이미 모듈이 있다면, 재설치하지 않습니다.
  • .pnp.cjs 파일을 remote repository에 추가합니다.

node_modules를 remote repository에 추가하는 것과 뭐가 다르죠?

  • 매우 다릅니다.
  • 1.2GB의 node_modules 디렉토리를 yarn berry는 139MB의 바이너리 파일로 저장합니다. Git은 1.2GB는 감당할 수 없지만, 139MB는 충분히 감당할 수 있습니다.
  • yarn classic에서 패키지를 하나 업데이트하려고 하면 매우 많은 양의 파일이 변경되거나 위치가 변경됩니다. yarn berry에서는 단 하나의 패키지 파일만 추가/삭제되기 때문에 성능과 보안 관점에서 유리합니다.

왜 프로젝트에 Yarn berry를 도입하였는가?

  • github actions 를 이용하여 CI pipeline을 구축하였습니다.

  • github actions는 각 job이 서로 다른 인스턴스 위에서 실행됩니다.

  • 초기화된 인스턴스에는 모듈이 설치되어 있지 않으므로 이를 설치하는 과정에서 꽤 많은 시간이 소요되고, 각각의 job에서 모듈을 설치하므로 시간이 많이 허비됩니다.

  • 모듈 설치시간을 단축시키기 위하여 yarn berry를 도입하여 zero-install 전략을 채택하였습니다.

도입 결과

yarn classic FE CI 시간(A)yarn berry FE CI 시간(B)diff(A-B)
5m 37s3m 31s2m 06s(37.39%) 단축

도입 전

도입 후

profile
작은 실패, 빠른 피드백, 다시 시도

0개의 댓글