의존성 디커플링 및 테스트 컨테이너화를 통한 컴파일 2.5배 가속
컴파일 시간은 개발 프로세스 전반에 걸쳐 변화하는 복잡한 내부 및 외부 의존성뿐만 아니라 운영 체제나 하드웨어 아키텍처와 같은 컴파일 환경의 변화로 인해 더 늘어날 수 있습니다. 다음은 대규모 AI 또는 MLOps 프로젝트에서 작업할 때 접할 수 있는 일반적인 문제들입니다:
지나치게 긴 컴파일 - 코드 통합은 매일 수백 번 수행됩니다. 수십만 줄의 코드가 있는 상황에서는 작은 변경 사항 하나만으로도 일반적으로 한 시간 이상 걸리는 전체 컴파일이 발생할 수 있습니다.
복잡한 컴파일 환경 - 프로젝트 코드는 CentOS 및 Ubuntu와 같은 다양한 운영 체제, GCC, LLVM, CUDA와 같은 기반 의존성, 그리고 하드웨어 아키텍처를 포함하는 서로 다른 환경에서 컴파일되어야 합니다. 또한 특정 환경에서의 컴파일은 일반적으로 다른 환경에서는 작동하지 않을 수 있습니다.
복잡한 의존성 - 프로젝트 컴파일에는 구성 요소 간 및 서드파티 의존성이 30개 이상 포함됩니다. 프로젝트 개발은 종종 의존성의 변경으로 이어지며, 이는 필연적으로 의존성 충돌을 유발합니다. 의존성 간의 버전 관리는 매우 복잡하여 의존성 버전을 업데이트하면 다른 구성 요소에 쉽게 영향을 미칩니다.
서드파티 의존성 다운로드가 느리거나 실패함 - 네트워크 지연 또는 불안정한 서드파티 의존성 라이브러리는 리소스 다운로드 지연이나 액세스 실패를 유발하여 코드 통합에 심각한 영향을 줍니다.
의존성을 분리하고 테스트 컨테이너화를 구현함으로써, 오픈 소스 임베딩 유사도 검색 프로젝트 Milvus에서 작업하는 동안 평균 컴파일 시간을 60% 줄일 수 있었습니다.
프로젝트의 의존성 분리
프로젝트 컴파일에는 일반적으로 많은 수의 내부 및 외부 구성 요소 의존성이 포함됩니다. 프로젝트에 의존성이 많을수록 이를 관리하는 것은 더 복잡해집니다. 소프트웨어가 성장함에 따라 의존성을 변경하거나 제거하는 것뿐만 아니라 그렇게 했을 때의 영향을 식별하는 것도 더 어렵고 비용이 많이 듭니다. 의존성이 제대로 작동하도록 보장하기 위해 개발 프로세스 전반에 걸쳐 정기적인 유지 관리가 필요합니다. 부실한 유지 관리, 복잡한 의존성 또는 결함 있는 의존성은 개발을 늦추거나 중단시키는 충돌을 유발할 수 있습니다. 실제로 이는 리소스 다운로드 지연, 코드 통합에 부정적인 영향을 미치는 액세스 실패 등을 의미할 수 있습니다. 프로젝트 의존성을 분리하면 결함을 완화하고 컴파일 시간을 줄여 시스템 테스트를 가속화하고 소프트웨어 개발에 불필요한 지연을 피할 수 있습니다.
따라서 프로젝트의 의존성을 분리할 것을 권장합니다:
- 복잡한 의존성을 가진 구성 요소를 분할합니다
- 버전 관리를 위해 서로 다른 저장소를 사용합니다.
- 구성 파일을 사용하여 버전 정보, 컴파일 옵션, 의존성 등을 관리합니다.
- 프로젝트가 반복됨에 따라 업데이트되도록 구성 파일을 구성 요소 라이브러리에 추가합니다.
구성 요소 간 컴파일 최적화 — 구성 파일에 기록된 의존성 및 컴파일 옵션에 따라 관련 구성 요소를 가져와 컴파일합니다. 바이너리 컴파일 결과와 해당 매니페스트 파일에 태그를 지정하고 패키징한 다음, 이를 개인 저장소에 업로드합니다. 구성 요소 또는 해당 구성 요소가 의존하는 구성 요소에 변경 사항이 없는 경우, 매니페스트 파일에 따라 컴파일 결과를 재생합니다. 네트워크 지연이나 불안정한 서드파티 의존성 라이브러리와 같은 문제의 경우, 내부 저장소를 설정하거나 미러 저장소를 사용하는 것을 시도해 보십시오.
구성 요소 간 컴파일을 최적화하려면:
1.의존성 관계 그래프 생성 — 구성 요소 라이브러리의 구성 파일을 사용하여 의존성 관계 그래프를 생성합니다. 의존성 관계를 사용하여 업스트림 및 다운스트림 종속 구성 요소 모두의 버전 정보(Git Branch, Tag, 및 Git commit ID), 컴파일 옵션 등을 검색합니다.
Figure 1.
2.종속성 확인 — 순환 종속성, 버전 충돌, 그리고 컴포넌트 간에 발생하는 기타 문제에 대한 알림을 생성합니다.
3.종속성 평탄화 — 깊이 우선 탐색(DFS)으로 종속성을 정렬하고, 중복 종속성을 가진 컴포넌트를 앞쪽으로 병합하여 종속성 그래프를 형성합니다.
Figure 2.
4.MerkleTree 알고리즘을 사용하여 버전 정보, 컴파일 옵션 등을 기반으로 각 컴포넌트의 종속성을 포함하는 해시(Root Hash)를 생성합니다. 컴포넌트 이름과 같은 정보와 결합되어, 이 알고리즘은 각 컴포넌트에 대한 고유 태그를 형성합니다.
Figure 3.
5.컴포넌트의 고유 태그 정보를 기반으로, 해당 컴파일 아카이브가 private repo에 존재하는지 확인합니다. 컴파일 아카이브가 검색되면 압축을 풀어 재생용 manifest 파일을 얻고, 그렇지 않으면 컴포넌트를 컴파일하고 생성된 컴파일 object 파일과 manifest 파일을 표시한 뒤 private repo에 업로드합니다.
컴포넌트 내 컴파일 최적화 구현 — 언어별 컴파일 캐시 도구를 선택하여 컴파일된 object 파일을 캐시하고, private repository에 업로드하여 저장합니다. C/C++ 컴파일의 경우 CCache와 같은 컴파일 캐시 도구를 선택하여 C/C++ 컴파일 중간 파일을 캐시한 다음, 컴파일 후 로컬 CCache 캐시를 아카이브합니다. 이러한 컴파일 캐시 도구는 컴파일 후 변경된 코드 파일을 하나씩 캐시하고, 변경되지 않은 코드 파일의 컴파일된 컴포넌트를 복사하여 최종 컴파일에 직접 참여할 수 있도록 합니다. 컴포넌트 내 컴파일 최적화에는 다음 단계가 포함됩니다:
- Dockerfile에 필요한 컴파일 종속성을 추가합니다. Hadolint를 사용하여 Dockerfile에 대한 규정 준수 검사를 수행해 이미지가 Docker의 모범 사례를 준수하는지 확인합니다.
- 프로젝트 sprint 버전(version + build), 운영 체제 및 기타 정보에 따라 컴파일 환경을 미러링합니다.
- 미러링된 컴파일 환경 컨테이너를 실행하고, 이미지 ID를 환경 변수로 컨테이너에 전달합니다. 이미지 ID를 가져오는 예제 명령은 다음과 같습니다: “docker inspect ‘ — type=image’ — format ‘{{.ID}}’ repository/build-env:v0.1-centos7”.
- 적절한 컴파일 캐시 도구를 선택합니다: 컨테이너에 들어가 코드를 통합 및 컴파일하고, private repository에서 적절한 컴파일 캐시가 존재하는지 확인합니다. 존재하면 지정된 디렉터리에 다운로드하고 추출합니다. 모든 컴포넌트가 컴파일된 후, 컴파일 캐시 도구가 생성한 캐시는 프로젝트 버전 및 이미지 ID를 기반으로 패키징되어 private repository에 업로드됩니다.
추가 컴파일 최적화
처음 구축한 이미지는 너무 많은 디스크 공간과 네트워크 대역폭을 차지하고 배포하는 데 시간이 오래 걸렸으므로, 다음 조치를 취했습니다:
- 이미지 크기를 줄이기 위해 가장 가벼운 기본 이미지를 선택합니다. 예: alpine, busybox 등.
- 이미지 레이어 수를 줄입니다. 종속성을 최대한 재사용합니다. 여러 명령을 “&&”로 병합합니다.
- 이미지 빌드 중간 산출물을 정리합니다.
- 가능한 한 이미지 캐시를 사용하여 이미지를 빌드합니다.
프로젝트가 계속 진행됨에 따라 컴파일 캐시가 증가하면서 디스크 사용량과 네트워크 리소스가 급증하기 시작했고, 일부 컴파일 캐시는 활용도가 낮았습니다. 그래서 다음과 같이 조정했습니다:
캐시 파일 정기 정리 — private repository를 정기적으로 확인하고(예: 스크립트 사용), 한동안 변경되지 않았거나 다운로드가 많지 않았던 캐시 파일을 정리합니다.
선택적 컴파일 캐싱 — 리소스를 많이 요구하는 컴파일만 캐시하고, 많은 리소스를 필요로 하지 않는 컴파일은 캐싱을 건너뜁니다.
컨테이너화된 테스트를 활용하여 오류를 줄이고 안정성과 신뢰성 향상
코드는 다양한 운영 체제(예: CentOS 및 Ubuntu), 기반 종속성(예: GCC, LLVM, CUDA), 특정 하드웨어 아키텍처를 포함하는 서로 다른 환경에서 컴파일되어야 합니다. 특정 환경에서 성공적으로 컴파일되는 코드가 다른 환경에서는 실패할 수 있습니다. 컨테이너 내부에서 테스트를 실행하면 테스트 프로세스가 더 빠르고 정확해집니다.
컨테이너화는 테스트 환경이 일관되도록 보장하고, 애플리케이션이 예상대로 작동하는지 확인합니다. 컨테이너화된 테스트 접근 방식은 테스트를 이미지 컨테이너로 패키징하고 진정으로 격리된 테스트 환경을 구축합니다. 우리 테스터들은 이 접근 방식이 매우 유용하다는 것을 알게 되었고, 결과적으로 컴파일 시간을 최대 60%까지 줄일 수 있었습니다.
일관된 컴파일 환경 보장 — 컴파일된 산출물은 시스템 환경의 변화에 민감하기 때문에, 서로 다른 운영 체제에서 알 수 없는 오류가 발생할 수 있습니다. 컴파일 환경의 변화에 따라 컴파일된 산출물 캐시에 태그를 지정하고 아카이브해야 하지만, 이를 분류하기는 어렵습니다. 그래서 이러한 문제를 해결하기 위해 컴파일 환경을 통합하는 컨테이너화 기술을 도입했습니다.
결론
프로젝트 종속성을 분석함으로써, 이 글은 컴포넌트 간 및 컴포넌트 내 컴파일 최적화를 위한 다양한 방법을 소개하며, 안정적이고 효율적인 지속적 코드 통합을 구축하기 위한 아이디어와 모범 사례를 제공합니다. 이러한 방법들은 복잡한 종속성으로 인해 발생하는 느린 코드 통합 문제를 해결하고, 컨테이너 내부의 작업을 통합하여 환경의 일관성을 보장하며, 컴파일 결과의 재생과 중간 컴파일 결과를 캐시하는 컴파일 캐시 도구의 사용을 통해 컴파일 효율성을 향상시키는 데 도움이 되었습니다.
위에서 언급한 사례들은 프로젝트의 컴파일 시간을 평균 60% 줄였으며, 코드 통합의 전반적인 효율성을 크게 향상시켰습니다. 앞으로도 컴포넌트 간 및 컴포넌트 내 컴파일을 계속 병렬화하여 컴파일 시간을 더욱 줄일 것입니다.
이 글에는 다음 출처가 사용되었습니다:
- “소스 트리를 빌드 수준 컴포넌트로 분리하기”
- “프로젝트에 서드파티 종속성을 추가할 때 고려해야 할 요소”
- “소프트웨어 종속성에서 살아남기”
- “종속성 이해하기: 소프트웨어 개발에서의 조정 과제에 관한 연구”
저자 소개
Zhifeng Zhang은 Zilliz.com에서 오픈 소스 벡터 데이터베이스인 Milvus를 담당하는 시니어 DevOps 엔지니어이자, 중국 LF 오픈 소스 소프트웨어 대학의 공인 강사입니다. 그는 Guangzhou의 Software Engineering Institute에서 사물 인터넷(IOT) 학사 학위를 받았습니다. 그는 CI/CD, DevOps, IT 인프라 관리, Cloud-Native 툴킷, 컨테이너화, 컴파일 프로세스 최적화 분야의 프로젝트에 참여하고 이를 이끄는 데 커리어를 보내고 있습니다.
계속 읽기

Zilliz Cloud Update: Smarter Autoscaling for Cost Savings, Stronger Compliance with Audit Logs, and More
What's new in Zilliz Cloud? Smarter autoscaling with scale-down, audit logs GA, enhanced SSO, and Milvus 2.6 in Private Preview.

Data Deduplication at Trillion Scale: How to Solve the Biggest Bottleneck of LLM Training
Explore how MinHash LSH and Milvus handle data deduplication at the trillion-scale level, solving key bottlenecks in LLM training for improved AI model performance.

The Great AI Agent Protocol Race: Function Calling vs. MCP vs. A2A
Compare Function Calling, MCP, and A2A protocols for AI agents. Learn which standard best fits your development needs and future-proof your applications.



