Rust의 Cargo target 디렉터리 디스크 사용량을 Nightly의 새로운 -Zno-embed-metadata 플래그로 상당히 줄일 수 있는 방법과 그 원리에 대해 다룹니다.
Rust(그리고 Cargo)에서 target
디렉터리의 디스크 사용량은 자주 언급되는 불편 사항입니다. 작년 연간 설문 결과에 따르면, Rust 사용자에게 세 번째로 큰 문제점이 바로 이 디렉터리의 크기였죠(가장 큰 문제는 느린 컴파일과 미흡한 디버깅 환경이었습니다1). Rust의 "모든 것을 소스에서 빌드" 모델, 그리고 dev
프로파일에서 debuginfo와 증분 빌드를 기본 활성화한 점을 고려하면, target 디렉터리가 아주 작아지긴 힘들 겁니다. 그래도, target 디렉터리의 크기를 실질적으로 줄이기 위한 방법은 계속 논의되고 있고, 본 글에서는 그 중 최신 방법을 소개합니다.
덧붙여, 시간의 흐름에 따라 target 디렉터리의 크기가 무한히 커지는 걸 막으려는 시도(예: Cargo Garbage Collection)도 있지만, 이 글은 일시적인 크기 감소 자체에 초점을 둡니다.
이 글의 핵심 주제는 아니지만, 배경 지식을 위해 어떤 파일이 실제로 target 디렉터리 공간을 잡아먹는지 간단히 살펴봅니다. 즐겨 사용하는 프로젝트 hyperqueue를 다양한 빌드 모드로 컴파일하여, 각각의 target 디렉터리 크기를 측정해봤습니다2. (Rustc 버전: 1.89.0-nightly (2eef47813 2025-05-22))
최적화 | Incremental | Debuginfo | 타겟 크기 [MiB] |
---|---|---|---|
No (dev ) | No | No | 462 |
No (dev ) | Yes | No | 677 |
No (dev ) | No | Yes | 870 |
No (dev ) | Yes | Yes | 1316 |
Yes (release ) | No | No | 396 |
결과에서 보듯, debuginfo와 증분 빌드 캐시가 디스크 공간을 크게 차지합니다. 이들만 줄여도 효과가 있겠지만, 실제로는 또 다른 비효율적 원인, 즉 Rust crate의 메타데이터가 target 디렉터리의 부풀어짐을 가중시키고 있습니다.
Rust 라이브러리 crate(rlib
)를 일반적으로 빌드하면, 컴파일러는 크게 두 가지 주요 산출물을 만듭니다:
여기서 재미있는 점은, Rust 의존 관계를 따라 코드를 컴파일할 때는 실제 오브젝트 코드는 필요하지 않고, 메타데이터만 있으면 된다는 사실입니다(오브젝트 코드는 최종 링킹 단계에서만 필요). Cargo는 이 원리를 이용해 파이프라이닝(Pipelining)이라는 빌드 최적화 기법을 적용합니다. 예를 들어, 바이너리가 crate B
에 의존하고, B
가 crate A
에 의존할 때 다음과 같이 됩니다.
text--- .rmeta of A generated v [-libA----|--------] [-libB----|--------] [-binary-----------] 0s 5s 10s 15s 25s
Cargo는 먼저 A
의 .rmeta
(메타데이터) 파일을 빨리 만든 뒤, 이 파일을 이용해 B
컴파일을 바로 시작합니다. 그렇게 각 crate의 빌드 과정을 겹치게 하여 전체 컴파일 시간도 줄여줍니다.
이 방식(Cargo의 기본 빌드 기법)은 오래전부터 써왔지만, 디스크 공간 측면에서는 한 가지 낭비가 존재합니다.
파이프라이닝 빌드 후에는 각 라이브러리마다 "메타데이터의 두 사본"이 디스크에 남습니다:
.rlib
(오브젝트 코드와 함께).rmeta
여기에 여러 Cargo 프로파일/플래그를 조합해 빌드할수록 문제는 가중됩니다.
추가로, 만약 라이브러리를 dylib
으로 빌드하면(예: 리눅스의 .so
), 여기에도 메타데이터가 저장되지만, 런타임에 함수 호출만 할 땐 이 정보가 필요 없습니다. Rust의 안정된 ABI가 없어 dylib 활용은 아직 드물지만, 이를 배포하는 일부 분들에겐 불필요한 파일 부피로 작용할 수 있습니다.
이 문제를 해결하려고, 예전부터 유명한 @bjorn3
가 메타데이터를 .rmeta
에만 넣고, .rlib
/.so
엔 넣지 않게 하는 컴파일러 플래그를 제안한 바 있습니다. 이미 프로토타입 구현도 있었기에, 스스로 동기부여를 받아 마침내 해당 플래그의 구현을 마무리했고, 밤하늘(야간 Rust)에 -Zembed-metadata=no
로 사용할 수 있게 되었습니다(병합됨).
이 플래그를 사용하면, .rlib
파일에는 최소한의 "메타데이터 스텁"만 남기고, 실제 메타데이터의 나머지는 .rmeta
로만 저장됩니다.
플래그가 도입되면, 보통은 RUSTFLAGS
로 간편히 실험이 가능해야 하는데, 이 플래그는 --emit=metadata
와 함께 써야 효과가 있습니다. 그렇지 않으면 메타데이터가 전혀 생성되지 않으니 조심하세요. 그리고 빌드 마지막에는 .rmeta
의 경로도 rustc
에 넘겨줘야 하므로, Cargo에도 별도의 지원이 필요했습니다. 그래서 해당 기능을 Cargo에도 구현하여, 새로운 불안정 Cargo 플래그 -Zno-embed-metadata
를 제공합니다.
-Zno-embed-metadata
사용 전/후로 hyperqueue 빌드 시 결과는 다음과 같습니다3:
최적화 | Incremental | Debuginfo | 적용 전 [MiB] | 적용 후 [MiB] | 감소 폭 |
---|---|---|---|---|---|
No (dev ) | No | No | 462 | 336 | -27.3% |
No (dev ) | Yes | No | 683 | 558 | -18.3% |
No (dev ) | No | Yes | 874 | 748 | -14.4% |
No (dev ) | Yes | Yes | 1325 | 1199 | -9.5% |
Yes (release ) | No | No | 397 | 253 | -36.3% |
디스크 절감 효과는 release 모드에서 가장 뚜렷하며, 일반적으로 debuginfo나 증분 빌드가 없을 때 크게 나타납니다. (이들은 용량이 워낙 커서 중복 메타데이터의 비중이 작아질 수 있습니다.)
개인적으론, 이로 인해 빌드 시간까지 단축되지 않을까 기대했지만, SSD 탑재 리눅스 기준 실험에서는 효과가 매우 미미했습니다.
추가로, 이 플래그를 도입한 이유 중 하나는 Rust 컴파일러 배포본(특히 표준 라이브러리 .so
파일) 사이즈를 줄이려는 목적이었습니다. 전체 벤치마크 테스트는 아직 진행 중이지만, x86_64-unknown-linux-gnu
표준 라이브러리 .so
파일이 약 13MiB에서 3MiB로 줄어드는 것을 확인했습니다.
크게 문제만 없다면, 이 동작(-Zno-embed-metadata
)을 기본값으로 삼아 모든 사용자의 target 디렉터리 디스크 점유를 줄였으면 합니다. 다만, Cargo 팀 쪽에선 .rlib
내 메타데이터에 의존하는 사용자 유무가 불확실해, 완전한 하위 호환성 깨짐 가능성이 있다고 봅니다. Cargo에선 문서화되지 않은 변경사항의 "파급 효과" 판단이 매우 어렵습니다(Hyrum's Law 참고).
따라서, nightly
툴체인에서 새 동작을 기본값으로 시간 한정 적용해 야생에서 문제를 찾아낸 뒤, opt-in이 아니라 opt-out 방식으로 플래그 의미를 뒤집는 식(즉, -Zembed-metadata
가 새 플래그로)으로 마이그레이션하면 좋겠다고 생각합니다4.
이 기능의 향후 상태는 rustc, Cargo 트래킹 이슈에서 확인하실 수 있습니다.
이 플래그가 실전 환경에서 얼마나 잘 동작하는지, 문제가 발생하지는 않는지, 디스크 공간 절감효과는 어느 정도인지 궁금합니다. 직접 실험해보고 싶으신 분은 최신 rustc 및 Cargo로 cargo +nightly build -Zno-embed-metadata
를 시도해보세요. 이 스크립트도 벤치마크 자동화를 위한 참고 자료로 쓸 수 있습니다.
실험 결과가 있다면 Reddit 등에 알려주시면 감사하겠습니다.