들어가며

소프트웨어 개발 환경에서, 패키지 관리 시스템은 개발자의 생산성과 코드 품질, 협업 효율성에 직결되는 중요한 요소입니다.

과거에는 npm과 Yarn Classic이 각자의 장단점을 가진 채 대표적인 패키지 매니저로 사용되었습니다. 특히 Yarn Classic은 npm보다 더 빠르고 일관된 설치 환경을 제공하여 많은 개발자들에게 사랑받았습니다.

그러나 대규모 프로젝트가 늘어나면서 Yarn Classic과 npm 모두 의존성 관리, 설치 속도, 확장성 측면에서 한계를 보이기 시작했습니다.

이러한 문제를 해결하기 위해 Yarn Berry가 탄생했습니다. Yarn Berry는 Yarn Classic의 다음 버전이자, 패키지 관리 시스템의 새로운 패러다임을 제시합니다.

이번 글에서는 Yarn Berry로 전환해야 하는 이유와 함께 장점들을 구체적으로 살펴보겠습니다.

Yarn Classic

Yarn Berry를 알아보기 전에, 우선 Yarn Classic에 대해 간단히 알아보겠습니다.

Yarn Classic은 2016년에 Meta(구 Facebook)이 npm의 성능과 의존성 관리 문제를 해결하기 위해 만든 패키지 매니저입니다. npm과 비교했을 때, Yarn은 다음과 같은 장점이 있습니다.

Yarn 의 장점

속도 (Parallel Installation & Offline Cache)

Yarn은 패키지 설치를 병렬로 처리하고, 오프라인 캐시 기능을 제공하여 npm보다 빠른 패키지 설치 속도를 자랑합니다.

Deterministic 설치 (yarn.lock)

Yarn은 yarn.lock 파일을 통해 항상 동일한 의존성 트리를 보장합니다. 이는 모든 개발 환경과 프로덕션에서 일관된 빌드를 가능하게 해 줍니다. npm도 package-lock.json을 도입했지만, Yarn의 이 기능은 더 오랫동안 안정성을 검증받았습니다.

Workspaces 기능

Yarn은 Workspaces를 통해 모노레포를 간편하게 관리할 수 있도록 해줍니다. 여러 프로젝트를 하나의 리포지토리에서 관리하는 데 유리하며, npm에 비해 이 기능이 더 오래 사용되어 안정성이 높습니다.

Yarn은 처음 등장했을 때 npm보다 더 나은 성능과 안정성을 제공하며 인기를 끌었지만, 시간이 지나며 단점들이 발견되기 시작했습니다.

Yarn의 단점

node_modules 폴더 의존

Yarn Classic은 npm과 동일하게 node_modules 폴더에 모든 의존성을 물리적으로 설치합니다. 결국 프로젝트가 커질수록 파일 구조가 비대해지고, 설치 속도와 성능이 저하됩니다. 특히, 디스크 공간 낭비와 파일 시스템의 I/O 성능 저하 문제가 큽니다.

비효율적인 설치

Yarn Classic은 패키지 설치 시마다 네트워크 요청이 필요해 설치 속도가 느리고, 오프라인 작업에 제한이 있습니다. 네트워크 상태에 의존적이므로, 환경에 따라 일관된 개발 경험을 제공하기 어렵습니다.

유령 의존성

유령 의존성(Phantom Dependency)은 프로젝트 내에 직접적으로 명시되지 않았지만, 의존성 트리 내의 다른 패키지에서 간접적으로 설치되는 의존성을 말합니다.

{
  "dependencies": {
    "packageA": "^1.0.0"
  }
}

위 예시에서, packageA는 프로젝트가 명시적으로 의존하는 패키지입니다.

하지만 packageA는 또 다른 패키지, 예를 들어 packageB를 의존할 수 있습니다. packageB는 하위 의존성이므로, package.json 파일에는 나타나지 않지만 node_modules에는 설치됩니다. 이렇게 간접적으로 설치된 packageB가 바로 유령 의존성입니다.

유령 의존성의 문제점

유령 의존성은 package.json에 명시되지 않기 때문에, 의존성 트리에서 어떤 패키지가 어떤 버전으로 설치되었는지 한눈에 파악하기 어렵습니다. 주로 하위 의존성에서 발생하는데, 문제는 하위 의존성이 여러 곳에서 중복되거나, 서로 다른 버전이 요구될 때 발생합니다.

예를 들어 packageA는 packageB@1.0.0을 요구하고, packageC는 packageB@2.0.0을 요구한다면, 두 버전이 충돌하게 됩니다. 이 충돌은 직접적으로 package.json 파일에서 관리되지 않기 때문에, 패키지 관리자들이 자동으로 충돌을 해결하지 못할 수도 있습니다.

결국 다양한 문제들로 인해 Yarn은 2020년 부터 유지보수 모드로 전환되며 Yarn Classic으로 이름이 바뀌게 되었습니다.

Yarn Berry의 등장

Yarn Berry는 Yarn 패키지 매니저의 새로운 버전으로, 이전 버전인 Yarn Classic과는 달리 구조적인 변화와 함께 새로운 기능들을 도입하였습니다.

Plug’n’Play (PnP): 더 빠르고 효율적인 의존성 관리

Yarn Berry의 가장 큰 혁신 중 하나는 Plug’n’Play (PnP) 시스템입니다. PnP는 node_modules 폴더를 완전히 없애고, .pnp.cjs 파일을 통해 패키지와 해당 의존성의 위치를 추적합니다.

기존 node_modules 방식의 문제점

기존의 node_modules 방식은 모든 의존성을 설치할 때, 모든 의존성 패키지를 물리적으로 디스크에 설치하는데, 이 때 패키지가 중첩된 트리 구조로 설치됩니다. 의존성 트리가 깊어질수록 패키지가 여러 계층에 중복되어 설치될 수 있습니다.

이로 인해 디스크 사용량 증가 및 설치 시간 지연 문제가 발생할 수 있고, 파일을 탐색할 때 시스템이 각 폴더를 검색해야 하므로 성능이 저하될 수 있습니다.

중첩 의존성이 많아지면 node_modules 트리는 매우 복잡해지고, 이로 인해 의존성 충돌이나 중복 문제가 발생할 수 있습니다.

PnP의 개선된 방식

패키지 매핑

PnP는 .pnp.cjs 파일을 생성하여 패키지들의 매핑 테이블을 유지합니다. 이 테이블은 어떤 패키지가 어떤 경로에서 로드될지를 정의합니다. PnP는 패키지를 로드할 때 이 매핑 테이블을 참조하여 패키지 간의 의존성을 빠르게 해결합니다.

캐싱과 압축

패키지 파일을 캐싱하여 압축된 상태로 저장하거나, 저장소에서 패키지를 바로 로드할 수 있게 최적화합니다. node_modules처럼 중복된 파일들을 물리적으로 계속 저장하지 않고, 필요할 때만 압축된 상태로 불러오기 때문에 디스크 사용량이 줄어들고, 설치 속도가 빨라집니다.

빠른 의존성 해결

의존성 해결 (Dependency Resolution) 은 프로젝트가 필요로 하는 모든 패키지를 올바른 버전으로 설치하고, 패키지 간의 충돌이나 중복을 방지하는 과정을 말합니다. Package Manager의 핵심 기능 중 하나이며, 자동화된 도구들이 이를 처리해 줌으로써 개발자는 의존성 문제에 덜 신경 쓰고 더 많은 시간을 코드 작성에 집중할 수 있게 됩니다.

Yarn PnP에서는 의존성 그래프가 .pnp.cjs 파일에 명시되어 있기 때문에, 패키지를 로드할 때 불필요한 파일 탐색 대신 매핑된 경로를 즉시 참조합니다. 이로 인해 의존성 해결 속도가 빨라지고, 불필요한 파일 탐색을 줄여 성능이 향상됩니다.

의존성 충돌 해결

기존의 node_modules방식에서는 의존성이 중첩되면서 충돌이 발생할 수 있습니다. 예를 들어, 서로 다른 패키지가 같은 의존성 패키지의 다른 버전을 필요로 하면, 여러 버전의 같은 패키지가 중첩된 형태로 설치됩니다.

/project-root
  /node_modules
    /packageA                # packageA의 의존성
      /node_modules
        /lodash (4.0.0)      # lodash@4.0.0이 중첩 설치됨
    /packageB                # packageB의 의존성
      /node_modules
        /lodash (3.0.0)      # lodash@3.0.0이 중첩 설치됨

결과적으로 디스크 공간을 불필요하게 차지하고, 복잡한 트리 구조가 형성됩니다. 이 때, 같은 의존성의 다른 버전이 상위 패키지에서 호출된다면 의도치 않은 버전이 로드되어 충돌이 발생할 수 있습니다.

node_modules 방식에서는 중복된 패키지를 줄이기 위한 호이스팅(hoisting) 이라는 방법을 사용합니다.

호이스팅(hoisting) : 패키지 관리자가 의존성을 관리할 때, 중복된 의존성 패키지를 줄이고 설치 효율을 높이기 위해 패키지들을 최상위 레벨로 끌어올리는 작업

다음은 위 예시에서 호이스팅이 일어난 경우입니다.

/project-root
  /node_modules
    /lodash (4.0.0)       # lodash@4.0.0이 상위 레벨로 호이스팅됨
    /packageA
      /node_modules
        # lodash@4.0.0을 사용 (정상)
    /packageB
      /node_modules
        # lodash@3.0.0이 필요하지만, 상위에서 lodash@4.0.0이 로드됨 (충돌 발생)

이 경우 packageB에선 lodash@3.0.0이 필요하지만, 상위 레벨에서 이미 lodash@4.0.0이 로드되어 버전 충돌이 발생합니다.

Yarn PnP는 패키지 설치 후, .pnp.cjs 파일을 생성하여 각 패키지가 어느 경로에 위치하는지에 대한 정보를 기록합니다. 이 파일은 프로젝트가 사용하는 모든 패키지와 그 의존성 패키지의 위치를 하나의 패키지로 관리합니다.

/project-root
  /.pnp.cjs              # 패키지 매핑 정보 파일
  /cache                 # 패키지 캐시 디렉토리
    /v6
      /npm-lodash-4.0.0  # lodash 4.0.0이 캐시에 저장됨
      /npm-lodash-3.0.0  # lodash 3.0.0이 캐시에 저장됨
{
  "packageA": {
    "dependencies": {
      "lodash": "4.0.0"
    }
  },
  "packageB": {
    "dependencies": {
      "lodash": "3.0.0"
    }
  },
  "lodash@4.0.0": {
    "location": "/cache/npm-lodash-4.0.0"
  },
  "lodash@3.0.0": {
    "location": "/cache/npm-lodash-3.0.0"
  }
}

이렇게 패키지의 위치를 명시적으로 관리함으로써, 중복된 패키지 설치를 줄이고, 의존성 충돌을 방지할 수 있습니다.

PnP는 특히 대규모 모노레포 프로젝트에서 강력한 성능을 발휘합니다. 프로젝트의 규모가 커질수록 PnP의 이점은 더욱 두드러집니다.

Zero Install: 네트워크에 의존하지 않는 설치 환경

Yarn Berry는 Zero Install 기능을 도입했습니다. 패키지를 설치할 때, .yarn/cache 폴더에 저장하여 네트워크에 의존하지 않고도 즉시 개발 환경을 구축할 수 있게 합니다.

Zero Install의 장점

오프라인 설치 가능: 한 번 설치한 패키지는 캐시에 저장되기 때문에, 네트워크에 연결되지 않은 상태에서도 패키지 설치가 가능합니다.

더 빠른 설치: 캐시에서 바로 패키지를 불러오기 때문에 설치 속도가 매우 빠르며, 네트워크 상태와 무관하게 일관된 속도를 유지합니다.

협업 시 일관성: 모든 팀원이 동일한 캐시 파일을 사용하므로, 환경 간 차이가 없어 협업이 더욱 원활해집니다.

플러그인 생태계와 확장성

Yarn Berry는 플러그인 기반 아키텍처를 도입해, 개발자가 필요한 기능을 자유롭게 확장할 수 있도록 합니다. 이를 통해 프로젝트의 요구에 맞게 Yarn을 유연하게 커스터마이징할 수 있습니다.

장점

확장성: 필요에 따라 새로운 기능을 플러그인으로 추가할 수 있으며, 불필요한 기능을 제거할 수 있습니다.

보안 및 성능 향상: 플러그인 생태계를 통해 보안 기능을 추가하거나, 특정 워크플로우에 맞춘 최적화를 수행할 수 있습니다.

마치며

npm과 Yarn Classic은 여전히 많은 프로젝트에서 사용되고 있지만, 그 한계는 점점 더 두드러지고 있습니다. 특히, 대규모 프로젝트나 팀 단위로 협업하는 경우, 의존성 관리, 설치 속도, 성능 문제는 지속적인 골칫거리로 작용할 수 있습니다.

Yarn Classic 또는 npm을 사용 중이라면, Yarn Berry로의 전환을 통해 더 나은 성능, 더 적은 문제, 더 큰 유연성을 경험할 수 있습니다. 이번 기회에 Yarn Berry로 전환해 보는 것은 어떨까요?