# Bundler가 uv만큼 빠를 수 있을까?

> Clean Markdown view of GeekNews topic #25520. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=25520](https://news.hada.io/topic?id=25520)
- GeekNews Markdown: [https://news.hada.io/topic/25520.md](https://news.hada.io/topic/25520.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-01-03T04:32:54+09:00
- Updated: 2026-01-03T04:32:54+09:00
- Original source: [tenderlovemaking.com](https://tenderlovemaking.com/2025/12/29/can-bundler-be-as-fast-as-uv/)
- Points: 3
- Comments: 2

## Topic Body

- **Bundler의 성능 한계**를 분석하며, Python의 패키지 관리자 **uv**가 빠른 이유를 비교  
- uv의 속도는 Rust 언어 때문이 아니라 **병렬 다운로드, 글로벌 캐시, 메타데이터 기반 의존성 처리** 등 구조적 설계 덕분임  
- Bundler는 **다운로드와 설치 과정이 결합되어 있어 병렬 처리에 제약**이 있으며, 이를 분리하면 큰 개선 가능  
- **글로벌 캐시 통합, 하드링크 설치, PubGrub 해석기 통합** 등으로 RubyGems와 Bundler의 중복을 줄일 수 있음  
- 언어 재작성 없이도 **대부분의 성능 향상은 Ruby 코드 내에서 달성 가능**, uv 수준의 속도에 근접할 수 있음  

---

### Bundler와 uv의 성능 비교
- RailsWorld에서 제기된 “왜 Bundler는 uv만큼 빠르지 않은가?”라는 질문을 계기로 **Bundler의 성능 병목**을 조사  
- 작성자는 Bundler가 uv 수준의 속도를 달성할 수 있다고 확신하며, **성능 차이는 언어가 아니라 설계의 문제**라고 명시  
- Andrew Nesbitt의 글 *“How uv got so fast”*를 인용해 uv의 핵심 최적화 방식을 Bundler에 적용 가능 여부로 분석  

### Rust로의 재작성 여부
- uv가 Rust로 작성된 점은 사실이나, **속도의 본질적 원인은 Rust 자체가 아님**  
- Bundler의 병목을 제거해 “Rust로 다시 쓰는 것만이 남은 개선책”이 된다면 그것이 성공이라 평가  
- Rust 재작성은 **기존 호환성 제약 없이 실험적 설계를 시도할 자유**를 제공하지만, 필수 조건은 아님  

### Bundler의 구조적 병목
- Bundler는 **gem 다운로드와 설치를 하나의 메서드에 결합**해 병렬 다운로드가 불가능  
  - 예시 코드에서 `install` 메서드가 `fetch_gem_if_not_cached`와 `install`을 연속 실행  
  - 이로 인해 의존 관계가 있는 gem(`a -> b -> c`)은 순차적으로만 설치됨  
- 실험 결과, 의존성이 있는 경우 9초 이상 소요되지만, 독립적인 gem(`d, e, f`)은 **병렬 다운로드로 4초 내 완료**  
- **다운로드와 설치를 분리**하면 의존성 규칙을 유지하면서도 병렬 처리 가능  
  - 네 단계(다운로드 → 압축 해제 → 컴파일 → 설치)로 분리 제안  
  - 순수 Ruby gem은 의존성 설치 순서를 완화해 추가 속도 향상 가능  

### 캐시 및 설치 최적화
- uv의 **글로벌 캐시와 하드링크 설치** 방식을 Bundler에도 적용 가능  
  - Bundler와 RubyGems는 현재 Ruby 버전별로 별도 캐시를 사용  
  - `$XDG_CACHE_HOME` 기반의 **공유 캐시**로 통합 필요  
  - 하드링크 설치는 캐시 통합 후 적용 가능  
- Bundler는 이미 **PubGrub 의존성 해석기**를 사용하지만, RubyGems는 여전히 molinillo를 사용  
  - 두 시스템의 **해석기 통합**이 기술 부채 해소의 핵심  

### Rust 관련 최적화 요소의 적용 가능성
- **Zero-copy 역직렬화**는 RubyGems의 YAML 파싱 단계에서 일부 적용 가능성  
- **Ruby의 GVL(Global VM Lock)** 은 IO 중심 작업에서는 병렬 처리에 큰 제약이 없음  
  - IO와 ZLIB 처리는 GVL을 해제하므로 병렬 실행 가능  
  - 단, 작은 파일 쓰기에서는 GVL 관리 오버헤드가 성능 저하 요인  
  - Ruby 내부에서 이를 개선하는 작업이 진행 중  
- **버전 비교 최적화**: uv는 버전을 `u64` 정수로 인코딩해 비교 속도를 높임  
  - Ruby에서도 `Gem::Version`을 정수 기반으로 변환해 **해석기 성능 향상** 가능  
  - 이미 관련 리팩터링 시도가 있었으나 **하위 호환성 문제로 보류**  

### 결론 및 향후 계획
- uv의 속도는 언어보다 **불필요한 작업을 제거한 설계** 덕분이며, Bundler도 같은 방향으로 개선 가능  
- RubyGems와 Bundler는 이미 **현대적 패키지 관리 구조를 갖추고 있어**, uv 수준의 속도 달성이 현실적  
- 가장 큰 과제는 **레거시 코드와 호환성 유지**  
- Rust로 재작성하지 않아도 **99%의 성능 향상은 Ruby 코드 내에서 가능**, 나머지 1%는 미미한 수준  
- 후속 글에서는 Bundler와 RubyGems의 실제 **프로파일링과 구체적 병목 원인**을 다룰 예정

## Comments



### Comment 48719

- Author: iolothebard
- Created: 2026-01-06T02:41:42+09:00
- Points: 2

Talk is cheap. Show me the code!

### Comment 48597

- Author: neo
- Created: 2026-01-03T04:32:54+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=46458302) 
- Bundler의 구조를 잘 아는 건 아니지만, 가장 큰 개선은 **uv의 캐시 설계**를 도입하는 것이라 생각함  
  uv가 빠른 이유 중 핵심이 캐시 구조에 있고, 이는 다른 언어나 생태계에서도 복제 가능함  
  다만 `requires-python`의 상한을 무시하는 부분은 성능 때문이 아니라 더 나은 **의존성 해결**을 위해서임  
  예를 들어 프로젝트가 Python 3.8 이상을 요구하지만, 어떤 의존성이 `<4` 제한을 걸면 Python 4에서 설치 불가해짐  
  uv는 모든 지원 버전에 대해 해결하므로 상한을 무시해도 시간 절약은 거의 없음  
  관련 논의는 [Python Discuss 포럼](https://discuss.python.org/t/requires-python-upper-limits/12663)에서 볼 수 있음

- PEP 658 이후 Python의 Simple Repository API가 메타데이터를 직접 제공하듯, RubyGems.org도 이미 비슷한 정보를 제공함  
  그런데 gem을 풀어서야 **native extension 여부**를 알 수 있음  
  그래서 이 정보를 RubyGems.org 메타데이터에 직접 추가하면 의존성 설치 트리를 완전히 병렬화할 수 있지 않을까 제안함
  - 나도 같은 생각을 했지만, gemspec의 정보와 RubyGems.org 메타데이터가 다를 가능성이 있음  
    예전에 RubyGems.org에서 일할 때, 메타데이터가 버전별로 추출된다는 걸 기억함  
    과거 버전의 gemspec을 다시 처리해야 하는데, 이는 **위험한 메타데이터 변경**이 될 수 있음  
    그래서 과거 버전에는 적용이 어렵겠지만, 앞으로는 unpack 없이 설치 순서를 알 수 있게 개선할 수 있을 것 같음

- Aaron이 Bundler를 **Rust로 재작성하기보다 실질적인 알고리즘 개선**에 집중하는 점이 마음에 듦
  - 속도 향상도 좋지만, 나는 Ruby 설치 자체를 관리해주는 기능이 더 필요함  
    여러 버전 관리 도구와 Ruby 버전이 뒤섞여 있는 **혼란스러운 환경**이 정말 답답함
  - Aaron이 Shopify 소속이라 그런지 gem.coop 프로젝트 언급이 없어서 복잡한 감정이 듦  
    문제는 단순히 속도가 아니라 **통제권과 생태계의 방향성**이라 생각함  
    Ruby는 지난 10년간 속도에 집중했지만, 문서 품질과 커뮤니티 관리가 오히려 더 중요했음  
    언어가 쇠퇴하는 이유를 진지하게 고민하고, 다양한 아이디어를 밀어붙여야 할 시점임

- 최근 관련 글로 [How uv got so fast](https://news.ycombinator.com/item?id=46393992) (2025년 12월, 457개 댓글)이 있음

- RubyGems를 더 빠르게 만들려면 각 gem의 파일 목록을 **레지스트리/데이터베이스화**하는 게 핵심임  
  이렇게 하면 `require` 시마다 파일 시스템을 스캔할 필요가 없음  
  gem을 직접 수정하면 메타데이터를 다시 해시해야 하지만, 어차피 수동 수정은 권장되지 않음
  - 예전에 이와 비슷한 코드를 작성했는데, 디스크 캐시는 없지만 **해시를 즉석에서 생성**해도 큰 속도 향상이 있었음  
    지금은 구식이겠지만 여전히 애착이 가는 미니 프로젝트임  
    코드: [fastup](https://github.com/pmahoney/fastup)
  - “bundle install” 최적화는 방향이 잘못된 접근임  
    진짜 문제는 `$LOAD_PATH`가 모든 gem을 추가해 **조합 폭발**을 일으키는 구조임  
    여러 캐시 프로젝트가 존재한다는 건 이게 실제 문제라는 증거임  
    예전에 앱 시작에 몇 분이 걸렸는데, load path를 조작해 **분 단위로 단축**한 적도 있음
  - 런타임에서 이걸 처리하려 했지만, Ruby에는 효율적인 데이터 구조가 부족해 구현이 어려웠음  
  - 사실 이건 이미 **bootsnap**이 하는 일임  
    예전에 bootsnap을 bundler에 통합하자고 제안했지만 거절당했음

- RubyGems의 구조 설명이 흥미로웠음  
  gem은 tar 파일이고, 그 안의 YAML GemSpec이 의존성을 선언함  
  RubyGems.org는 이 정보를 API로 제공하므로 eval 없이도 의존성 확인이 가능함  
  다만 YAML은 **파싱 효율이 낮은 포맷**이라, JSON이나 protobuf 같은 대안이 더 나을 수도 있음  
  그래도 gemserver가 이미 의존성 정보를 반환한다면 큰 문제는 아닐 듯함
  - YAML이 별로긴 하지만, 일반적인 gemspec 크기에서는 **성능 영향이 미미**할 것 같음  
  - 사람이 수정하지 않고 검토만 하는 용도의 lockfile이라면, YAML의 복잡한 기능을 제거한 **단순 파서**를 만들 수 있음  
    예: 버전, 의존성, 해시 정도만 포함하는 구조  
  - 사실 이런 메타데이터는 RubyGems나 PyPI가 **데이터베이스에 미리 파싱해 저장**함  
    uv가 빠른 이유도 여기에 있음 — 패키지를 다운로드하지 않고도 의존성 계산이 가능함

- 예전에 gem 설치 방식을 개선한 **프로토타입 영상**을 만든 적 있음  
  [how_gems_should_be.mov](https://ra66i.org/tmp/how_gems_should_be.mov)
  
- Ruby의 **fibers(또는 Async 라이브러리)** 는 종종 과대평가됨  
  스레드와 마찬가지로 커넥션 풀 같은 상위 레벨 조정 문제가 여전히 존재함  
  그래도 IO 바운드 설치 작업을 비동기로 처리하면 **의미 있는 성능 향상**을 볼 수 있음  
  - 순수 Ruby에서 더 짜내려면,  
    1) 파싱이 빠른 인덱스 포맷 사용 ([관련 gist](https://gist.github.com/raggi/4957402))  
    2) 초기 다운로드는 스레드로 처리  
    3) 압축 해제와 post-install은 fork로 분리  
    이런 식으로 접근할 것 같음

- “**글로벌 캐시**를 모든 bundler 인스턴스가 공유”하는 아이디어를 검토 중임  
  장기적으로는 큰 이득이 있을 것 같지만, 숨은 복잡성이 있는지 판단 중임  
  관련 이슈: [rubygems #7249](https://github.com/ruby/rubygems/issues/7249)
  - 완전히 단순하진 않지만, 다른 생태계의 **선행 사례**를 참고하면 충분히 가능함  
    Ruby가 처음 이 문제를 푸는 건 아니니, 이제는 그 혜택을 누릴 때임

- 최적화의 기본 원칙은 간단함 — **아무것도 하지 않는 게 가장 빠름**
  - “똑똑한 코드가 빠르다”는 착각을 버려야 함  
    **불필요한 일 자체를 안 하는 것**이 진짜 최적화임
