uv가 이렇게 빨라진 이유
(nesbitt.io)- Python 패키지 관리자 uv는 pip보다 10배 이상 빠른 설치 속도를 보이며, 이는 단순히 Rust로 작성되었기 때문이 아니라 설계상의 선택에서 비롯됨
- 속도를 가능하게 한 핵심은 정적 메타데이터 표준(PEP 518, 517, 621, 658) 으로, 코드 실행 없이 의존성을 파악할 수 있게 함
- uv는 pip이 유지하는 레거시 기능(.egg, pip.conf, 시스템 Python 설치 등) 을 과감히 제거해 불필요한 코드 경로를 없앰
- Rust가 기여한 부분은 제로-카피 역직렬화, 락 없는 동시성, 단일 바이너리 구조 등으로, 전체 속도 향상 중 일부만 차지함
- 전체적으로 uv의 사례는 표준화된 메타데이터와 불필요한 호환성 제거가 성능 혁신의 핵심임을 보여줌
uv의 속도를 가능하게 한 표준
- pip의 느림은 구현 문제가 아니라, 과거 setup.py 기반 구조로 인해 의존성을 알기 위해 코드를 실행해야 했던 구조 때문임
- setup.py 실행에는 빌드 의존성 설치가 필요했고, 이는 “닭과 달걀 문제” 를 초래
- 설치 과정에서 임의 코드 실행과 반복 실패가 발생, 설치 속도를 저하시킴
-
PEP 518(2016) 은
pyproject.toml을 도입해 코드 실행 없이 빌드 의존성을 선언 가능하게 함 - PEP 517(2017) 은 빌드 프런트엔드와 백엔드를 분리, pip이 setuptools 내부를 이해할 필요를 제거
-
PEP 621(2020) 은
[project]테이블을 표준화해 TOML 파싱만으로 의존성 확인 가능 - PEP 658(2022) 은 패키지 메타데이터를 Simple Repository API에 직접 포함시켜, 휠 다운로드 없이 의존성 정보를 가져올 수 있게 함
- PyPI는 2023년 5월 PEP 658을 적용했고, uv는 2024년 2월 출시되어 새 표준 인프라를 완전히 활용한 첫 도구로 등장
- Rust의 Cargo나 npm처럼, Python 생태계도 이제 정적 메타데이터 기반 패키징으로 전환됨
uv가 제거한 기능들
- uv의 속도는 불필요한 기능 제거에서 비롯됨
- .egg 지원 없음: pip은 여전히 처리하지만 uv는 완전히 배제
- pip.conf 무시: 설정 파일, 환경 변수, 상속 로직을 모두 생략
-
바이트코드 컴파일 비활성화:
.py를.pyc로 변환하지 않아 설치 시간 단축 - 가상환경 필수화: 시스템 Python에 직접 설치하지 않아 권한 검사와 안전성 코드 제거
- 엄격한 스펙 준수: 잘못된 패키지를 거부해 예외 처리 로직 축소
-
requires-python 상한 무시:
python<4.0같은 방어적 제약을 무시해 의존성 해석(backtracking) 감소 - 첫 번째 인덱스 우선: 여러 인덱스 중 첫 번째에서 패키지를 찾으면 즉시 중단, 의존성 혼동 공격 방지 및 네트워크 요청 절감
- 이 모든 항목은 pip이 수행해야 하는 코드 경로를 uv가 제거한 사례임
Rust 없이 가능한 최적화
- uv의 속도 중 상당 부분은 언어와 무관한 설계 최적화에서 비롯됨
- HTTP Range 요청으로 휠 파일의 중앙 디렉터리만 부분 다운로드, 전체 파일 다운로드를 피함
- 병렬 다운로드로 여러 패키지를 동시에 가져옴
- 글로벌 캐시와 하드링크를 사용해 동일 패키지를 여러 가상환경에 설치해도 디스크 공간 추가 소모 없음
- Python 비의존적 해석: TOML과 휠 메타데이터를 직접 파싱, setup.py만 있는 경우에만 Python 실행
- PubGrub 의존성 해석 알고리듬 사용으로 pip의 백트래킹 방식보다 빠르고 오류 설명이 명확함
Rust가 실제로 기여한 부분
- Rust는 특정 저수준 최적화에서 중요한 역할을 함
- rkyv 기반 제로-카피 역직렬화로 캐시 데이터를 복사 없이 직접 사용
- 락 없는 동시성 구조체로 안전한 병렬 접근 구현
- 인터프리터 초기화 없음: uv는 단일 정적 바이너리로, pip의 Python 프로세스 생성 비용 제거
- 버전 정보를 u64 정수로 압축 표현, 비교 및 해시 연산을 빠르게 수행
- 이러한 요소들은 성능을 높이지만, 전체 속도 향상의 주된 원인은 아님
핵심 교훈
- uv의 속도는 Rust 때문이 아니라, 하지 않는 것들 덕분임
- PEP 518·517·621·658의 표준화가 빠른 패키지 관리의 기반을 마련했고, uv는 레거시 제거와 현대적 가정으로 이를 실현
- pip도 병렬 다운로드, 글로벌 캐시, 메타데이터 기반 해석을 구현할 수 있지만, 15년간의 하위 호환성 유지가 장애 요인
- 결과적으로 pip은 항상 느릴 수밖에 없고, 새로운 전제에서 출발한 도구만이 근본적 속도 향상 가능
- 다른 패키지 관리자에게 주는 교훈은, 정적 메타데이터·코드 실행 없는 의존성 탐색·사전 해석 가능 구조가 필수라는 점임
- Cargo와 npm은 이미 이 방식을 채택하고 있으며, 의존성 확인을 위해 코드를 실행해야 하는 생태계는 근본적으로 느림
Hacker News 의견들
-
이 글이 uv의 성능을 다각도로 잘 설명했다고 생각함
Rust로 작성된 점도 도움이 되지만, 지난 10년간 Python 생태계가setup.py의존에서 벗어나도록 만든 표준화 노력이 훨씬 큰 역할을 했음- 예전에 Haskell 프로젝트를 진행할 때, 언어 자체보다 전문가 커뮤니티를 선별할 수 있다는 점이 장점이었음
Rust도 비슷하게 커뮤니티의 역량을 끌어올리는 이유로 선택할 만함 - 많은 Rust 리라이트 프로젝트가 이런 후광 효과를 얻음
기존의 시행착오를 되짚으며 더 나은 설계를 할 수 있고, Rust 자체의 장점도 더해져 일종의 ‘원투 펀치’가 됨
- 예전에 Haskell 프로젝트를 진행할 때, 언어 자체보다 전문가 커뮤니티를 선별할 수 있다는 점이 장점이었음
-
“uv는 Rust라서 빠른 게 아니라, 하지 않는 일이 많아서 빠르다”는 주장에 공감함
다만 벤치마크 없이 속도 요인을 단정하는 건 이르다고 봄
PEP 518, 517, 621, 658의 영향은 크지만, 호환성 제거가 얼마나 기여했는지는 의문임
또 언어 선택이 최적화에 어떤 영향을 줬는지도 다뤄지지 않음
Cargo의 TOML 파서가 Python보다 훨씬 빠르다는 점도 흥미로움- pip의 과거 버전과 현재 버전을 비교하는 것도 일종의 벤치마크가 될 수 있음
실제로 TOML은 빌드 시에만 읽히므로 큰 비중은 없지만, wheel 보급이 속도 향상에 기여했음
관련 참고글: setup.py deprecated, wheels are faster
- pip의 과거 버전과 현재 버전을 비교하는 것도 일종의 벤치마크가 될 수 있음
-
rkyv를 이용한 zero-copy deserialization은 Rust만의 기술은 아님
C/C++ 같은 저수준 언어에서도 가능함- 여기서 “Rust 전용”이라 한 건 “Python에서는 불가능하다”는 의미로 이해함
“인터프리터 시작이 없다”는 것도 같은 맥락임 - Python으로는 zero-copy를 구현하기 어렵기 때문에, Rust가 이를 안전하고 쉽게 만든 점은 인정함
- 결국 이 논의는 Rust 대 Python의 비교임
- 여기서 “Rust 전용”이라 한 건 “Python에서는 불가능하다”는 의미로 이해함
-
글의 내용은 좋지만, LLM이 다듬은 문체가 너무 인공적으로 느껴짐
언젠가는 LLM으로 인해 망가진 글을 다시 인간적으로 복원하는 시대가 올지도 모름- 작성자는 SBOM과 Lockfile 관련 글로도 HN에 올라온 적 있음
공급망 보안 전문가로 보이지만, 그 글도 LLM 특유의 모호한 문체로 변질된 느낌이었음 - 반대로 어떤 사람은 LLM 냄새를 전혀 못 느끼고 자연스럽다고 생각했음
- 나는 이제 글에서 AI 냄새가 나면 바로 닫음
고정된 프롬프트가 모든 글을 비슷하게 만들어버려서, 인터넷이 전부 같은 목소리로 들리는 게 아쉬움
- 작성자는 SBOM과 Lockfile 관련 글로도 HN에 올라온 적 있음
-
uv의 속도에 열광하는 분위기가 이해되지 않음
대부분의 Python 사용자는 패키지 설치 속도를 상위 10개 고민거리에도 두지 않을 것 같음
나도 매일 Python을 쓰지만 체감이 크지 않음- 예전 회사에서는
poetry로 의존성 업데이트에 5~30분이 걸렸음
실패라도 하면 다시 30분 기다려야 했는데, uv는 정말 쾌적한 경험이었음 - 20년 넘게 Python을 써왔는데,
pip install이 배포 시간의 큰 비중을 차지했음
캐싱으로 속도를 높이려 애쓴 시간이 많았음 - 내 업무용 모놀리식 앱에서
poetry install은 2분,uv sync는 몇 초면 끝남
CI마다 2분씩 절약되니 누적 효과가 큼 -
uvx sometool을 실행할 때도 가상환경 생성과 의존성 설치가 몇 초면 끝나서 작업 흐름이 완전히 바뀜 - 오랜 Python 사용자로서 uv의 속도는 “삶의 질 변화급”임
이제 uv 없는 프로젝트로 돌아가기가 힘듦
- 예전 회사에서는
-
uv의 일부 속도 최적화 기법은 pip에도 이식 가능해 보임
예: 병렬 다운로드,.pyc지연 생성, egg 무시, 버전 체크 등
하지만 uv가 venv를 너무 잘 처리해서 굳이 pip를 손볼 필요는 없을 듯함
결국 “Rust 덕분만은 아니다”라는 점에서 pip도 더 빨라질 여지가 있음 -
빠른 언어를 선택하는 프로그래머는 이미 성능 최적화 마인드셋을 가진 경우가 많음
언어 자체보다 그런 태도가 성능을 좌우함 -
uv가
python<4.0상한을 무시하는 이유가 흥미로움
대부분의 패키지가 Python 4에서 깨질까 봐 방어적으로 설정한 것이지, 실제로는 문제없음
상한 제한은 현실적인 문제보다 가상의 문제를 해결하려는 시도였음- uv가 모든 상한을 무시하는 건 아니고, 4.0 한정으로 무시하는 것 같음
python<3.0같은 제약은 여전히 중요하므로, 그런 경우는 막아야 함
- uv가 모든 상한을 무시하는 건 아니고, 4.0 한정으로 무시하는 것 같음
-
PEP 658이 2023년에 적용되고 uv가 2024년에 등장한 건 우연이 아님
생태계가 준비되었기에 uv 같은 도구가 가능해졌음
그런데 패키지 유지보수자들이 왜 이런 변화를 받아들였는지 궁금함
setup.py가 잘 작동하던 사람들도 있었을 텐데, pyproject.toml로 옮긴 동기가 무엇이었을까- 사실
setup.py는 많은 사람에게 불편했음
예를 들어 Requests조차 아직 완전한 PEP 517/518/621 호환이 아님
1년 반이 지나도 minor 릴리스가 지연되고, 그 사이 빌드 문제도 발생했음 - 정적 선언이 더 안전하고 성능상 유리했기 때문임
다만 pip가 왜 이를 충분히 활용하지 않는지는 의문임
- 사실
-
“하지 않는 코드 경로는 기다릴 필요가 없다”는 표현은 부정확함
실행하지 않는 코드만이 시간을 절약함
예를 들어.egg지원이 없어도 이미 폐기된 포맷이라면 속도에 영향이 없음
실제로 어떤 항목이 얼마나 시간을 절약했는지 정량적 데이터가 있으면 더 좋았을 것임
그래도 전반적으로 잘 정리된 글임