yarn berry workspace와 모노레포 도입 여정 🏃🏼‍♂️ (feat. Turborepo)

2023.09.03
16분
댓글

글을 시작하며

프로젝트가 성장하면서 규모가 커질 때 우리는 효율적인 프로젝트 구조를 고민합니다.
서비스는 늘어가는데 코드 관리는 점점 어려워지고 비효율적인 작업이 늘어간다면..? 이번 글에 주목해주시길 바랍니다.

Google, Facebook, Microsoft 등 글로벌 테크 회사들은 이미 효율적인 코드 관리를 위해 대규모 모노레포를 운영하고 있습니다.

이번 글에서는 현업에서 모노레포를 도입했던 배경과 모노레포의 단점을 극복하기 위해 시도했던 방법들을 공유하고자 합니다. 그리고 모노레포 구축 툴인 yarn workspaceTurborepo를 비교하면서 어떤 툴을 사용하면 좋을 지 알아보겠습니다.


모노레포 등장 배경

모노레포란 멀티레포와 반대되는 개념으로 두 개 이상의 프로젝트 코드를 하나의 레포지토리에서 관리하는 방식을 의미합니다.

모노레포는 멀티레포의 문제점들을 어떻게 해결하였는지 함께 살펴보겠습니다.

모노레포가 해결한 문제

  • 프로젝트 생성에 대한 오버헤드 감소
    멀티레포에서는 공유 패키지를 만들 때 매번 다음과 같은 과정을 거칩니다.

    저장소 생성 > 커미터 추가 > 개발환경 구축 > CI/CD 구축 > 빌드 > 패키지 저장소에 publish

    하지만 모노레포에서는 기존의 DevOps를 이용하므로 새로운 프로젝트 생성에 대한 오버헤드가 없습니다.

  • 쉬운 패키지 공유
    멀티레포의 경우 패키지 간 공유가 쉽지 않기 때문에 중복 코드가 발생할 위험이 있지만, 모노레포의 경우 모듈에 대한 공유가 수월하기 때문에 중복 코드가 발생할 위험이 적습니다.

  • 일관된 개발자 경험
    애플리케이션을 일관되게 구축하고 테스트할 수 있습니다. 하나의 저장소에서 패키지들을 관리하기 때문에 이슈 트래킹을 분산 없이 하나로 처리가 가능합니다.

기술을 도입할 때는 단점 또한 분명히 파악하는 것이 중요합니다.


모노레포의 단점

  • 용량 문제와 성능 이슈
    멀티레포 구조에서는 필요한 프로젝트만 설치할 수 있지만 모노레포에서는 프로젝트들의 모든 리소스를 관리하기 때문에 용량과 성능면에서 문제가 생길 수 있습니다.

  • 느린 CI Build
    멀티레포와 반대로 CI가 하나로 구성된다는 장점이 있지만, 패키지 규모가 커짐으로써 분산된 CI Build보다 속도가 느릴 수 밖에 없습니다.

  • 무분별한 의존성
    하나의 저장소에서 관리하기 때문에 패키지 간의 의존성 관리가 쉽다는 장점이 있지만 오히려 과도한 의존 관계가 나타날 수 있습니다.

그렇다면 모노레포를 어떤 상황에서 도입하는게 적절할까요?


모노레포 고려 상황

  • 서로 다른 패키지가 연관 관계를 가질 경우
  • N개의 패키지의 형태와 목적이 유사한 경우
  • N개의 패키지 중 배포되어야 할 패키지의 비중이 큰 경우

저의 경우 현업에서 웹뷰를 개발하면서 모노레포를 도입하였습니다.
배경은 하나의 디자인 시스템을 따르는 5가지 서비스의 웹뷰를 빠르게 개발해내는 것이 필요했습니다.

따라서 아래 세 가지의 요소를 효율적으로 처리하는 것이 중요하였고 모노레포의 장점을 활용해볼 수 있겠다는 판단이 들었습니다.

  • 세팅 시간 단축
  • 공통 컴포넌트 관리
  • 이슈 트래킹을 한 번에 처리

지금부터는 사용했던 모노레포 구축 툴모노레포의 단점을 극복하기 위해 시도했던 방법들에 대해서 공유하고자 합니다.


모노레포 구축 툴

모노레포를 구축하기 위해 사용되는 툴은 Yarn workspace, Turborepo, Lerna, Nx 등이 있습니다.

230904-231926

이번 글에서는 Yarn workspace와 Turborepo에 집중해보겠습니다.

1. Yarn workspace

  • yarn berry와 호환성이 좋으며 간단하게 모노레포를 구성할 수 있습니다.

2. Turborepo

  • 가장 최근에 출시되었으며 Vercel에서 운영하고 있습니다.
  • 초기 설정이 쉬우며 병렬처리 기법과 원격 캐싱을 이용해서 빌드 속도가 빠른 장점이 있습니다.

Yarn workspace

저는 현업에서 yarn workspace를 이용하여 모노레포를 구축하였습니다.
그 이유는 yarn berry를 사용하여 모노레포의 단점을 극복하기 위함이었습니다.

채택 이유

  • node_modules 문제 해결

    모노레포에서는 node_modules를 루트 위치에 두고 있기 때문에 프로젝트가 많아질 수록 빌드 속도가 느려지고 사용하지 않는 패키지를 설치하는 의존성 문제가 있었습니다.

    따라서 yarn berry의 PnP 방식을 도입하여 node_modules의 용량 및 의존성 문제를 해결하고자 하였습니다.

    결론적으로는 yarn berry를 사용하여 의존성 파일 크기를 60% 가량 줄이고 빌드 시간을 60초 이상 단축할 수 있었습니다.

  • yarn berry와의 호환성

    Turborepo의 경우 yarn berry의 PnP 모드가 온전히 지원되지 않기 때문에
    yarn berry와의 호환성이 좋은 yarn workspace를 채택하였습니다.

모노레포의 단점 극복 방법

  • 큰 용량과 성능 문제

    프로젝트가 많아질수록 clone시에 용량으로 생기는 문제들이 있었습니다.
    위의 문제는 git sparse checkout를 이용하여 원하는 디렉토리만 checkout 하는 형식으로 해결하였습니다.

    230904-234635

    위와 같이 client/android 디렉토리만 사용하고 싶은 경우 아래의 명령어를 입력하면 됩니다.

    git sparse-checkout set client/android
    

  • 무분별한 의존성

    node_modules의 의존성 문제를 yarn berry의 PnP 모드를 이용하여 해결하였습니다.

    • node modules의 의존성 탐색
      기존에 사용하고 있던 의존성 디렉토리는 Tree 구조로 Linked List의 탐색 특징을 가지고 있었습니다. 보통의 경우 O(N)만큼 탐색을 진행해야 하며 File I/O 연산을 하기 때문에 오랜 시간이 걸리는 문제가 있습니다.

    • PnP 모드의 의존성 탐색
      하지만 PnP 모드를 사용하면 의존성에 대한 Mapping Table을 만들어서 탐색하기 때문에 O(1)의 성능을 가지며 JSON 데이터 타입을 사용하여 메모리 내에서 연산을 하기 때문에 File I/O보다 훨씬 좋은 성능을 보여줍니다.

      게다가 의존성에 대한 정보를 압축하여 .zip 파일로 관리하기 때문에 용량면에서도 이점을 가집니다.

yarn berry로 인해 많은 장점을 누릴 수 있었지만 사용에 겪었던 어려움들도 있었습니다.

yarn berry 사용에서의 어려움

  • 여전한 install 과정

    모든 패키지의 dependency를 zip으로 관리할 수는 없었습니다.
    예를 들어 sentry-cli의 경우 로컬 환경에서의 의존성을 가지기 때문에 install 과정을 거치면서 환경 정보를 읽는 것이 필요했습니다.
    따라서 zero install의 장점을 온전히 누리기는 어려웠던 아쉬움이 있었습니다.

  • 의존성 관리

    yarn berry의 장점으로는 패키지의 호이스팅을 허용하지 않는 특성이 있습니다.
    하지만 모노레포에서도 이러한 특성이 적용되기 때문에 모듈은 루트에서 추가했는지와는 상관없이 무조건 하위 프로젝트의 의존성에 추가해야 하는 번거로움이 있었습니다.

  • PnP를 지원하지 않는 패키지

    PnP를 지원하지 않는 패키지로 인해 node_modules가 여전히 남아있는 문제가 있었습니다.
    이 문제는 패키지의 버전을 업데이트 하거나 다른 라이브러리로 교체하여 해결하였습니다.


Turborepo

Turborepo는 Vercel에서 운영하는 모노레포 툴이며 캐싱을 이용하여 빌드 속도가 빠르다는 장점이 있습니다.

Turborepo의 주된 사용 이유인 캐싱에 대해 알아보도록 하겠습니다.

캐싱

빌드 명령어를 실행하면 node_modules/.cache/turbo 디렉토리가 생성되고 디렉토리 내에 빌드 캐시가 저장됩니다.

230905-150550

만약 하나의 서비스만 수정을 하고 다시 빌드한다면, 수정된 서비스만 빌드를 진행하고 다른 서비스들은 캐시된 파일을 이용하여 빠르게 빌드할 수 있습니다.

모노레포 환경에서 개발할 때는 특정 서비스만 개발하고 배포하는 경우가 많기 때문에 Turborepo의 캐싱 기능은 분명 큰 장점입니다.

원격 캐싱

원격 캐싱을 이용하면 Vercel의 캐싱 서버나 소유하고 있는 캐싱 서버에 빌드 캐시를 저장하고 이를 CI에 활용할 수 있습니다.

예를 들어, Github에 push가 될 때마다 Vercel이 저장된 원격 캐시를 이용해서 빌드를 하기 때문에 빌드 시간을 크게 단축할 수 있습니다.

yarn berry

Turborepo에서 캐싱 전략을 통해 빠르게 빌드할 수 있다는 점에 대해 알게 되었습니다.
그렇다면 빌드 속도를 개선하였기 때문에 yarn berry는 사용할 필요가 없을까요?

Turborepo는 yarn berry의 핵심 기능인 PnP모드를 지원하지 않기 때문에 yarn classic을 사용해도 빌드와 배포에 문제는 없습니다.

하지만 yarn classic의 유지 보수가 종료된 상황이고, 아직도 node_modules의 큰 용량과 의존성 문제는 남아있기 때문에 프로젝트가 커질수록 yarn berry의 사용 또한 고려하는 것이 필요하다고 생각합니다.

yarn berry 적용 방법

yarn berry의 기능을 온전히 활용하려면 PnP 모드를 사용해야하지만 Turborepo는 PnP 모드를 완전히 지원하지 않습니다.
따라서 yarn berry에서 제공하는 nodeLinker를 이용하여 node_modules를 사용합니다.

yarn set version berry

위의 명령어를 입력하면 .yarn 폴더와 .yarnrc.yml 파일이 생성됩니다.
그 다음 터미널에서 패키지를 설치하면 .yarn/cache에 패키지의 캐시 파일들이 저장됩니다.

yarn install

230905-153434

yarn berry의 zero-install을 이용하기 위해서는 저장소에 캐시를 포함해야 하기 때문에 .gitignore에 아래의 값들을 추가합니다.

.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

비록 PnP 기능은 사용할 수 없었지만 캐시를 활용하여 yarn berry의 zero-install 전략은 사용할 수 있게 되었습니다.


글을 마치며

이번 글에서는 모노레포를 도입했던 배경과 모노레포의 단점을 극복하기 위해 시도했던 방법들에 대해서 알아봤습니다. 모노레포 도입을 고민하고 계시거나 어떤 툴을 이용하는게 적절할지 고민하시는 분들에게 도움이 되었으면 좋겠습니다.

멀티레포에서 모노레포로 코드를 이관하는 것은 분명히 간단한 작업은 아니지만, 환경에 맞는 기술을 충분히 검토하고 적절한 솔루션을 도출해낸다면 개발 생산성을 크게 높일 수 있을 것이라 생각합니다.

참고 문서