큐레이션 요약
더 나은 소프트웨어를 만들기
빌드가 60분씩 걸리면 코드 변경에 대한 피드백이 늦어져 개발 생산성이 크게 떨어진다. Slack은 Bazel과 전통적인 성능 최적화 원칙인 캐싱·병렬화를 결합해 빌드 시간을 개선했다. 핵심은 빌드를 명확한 입력·출력 단위의 그래프로 모델링하고, 재사용 가능하고 병렬 실행 가능한 작은 작업으로 나누는 것이다.
빌드를 방향성 비순환 그래프로 모델링
- 백엔드와 프런트엔드는 서로 독립적으로 개발·배포될 수 있으며, 각각 필요한 소스 파일에만 의존한다.
- 빌드 구성 요소와 배포 산출물 사이의 의존성은 방향성 비순환 그래프(DAG)로 표현된다.
- 특정 Python 파일이 변경되면 백엔드만 다시 빌드하고, TypeScript 파일 변경 때문에 백엔드까지 재빌드하지 않도록 의존성을 정확히 정의할 수 있다.
- 그래프로 빌드를 표현하면 애플리케이션 코드와 동일하게 다음 최적화를 적용할 수 있다.
- 이미 수행한 작업의 결과를 저장해 다시 계산하지 않기
- 여러 컴퓨팅 자원에 작업을 분산해 동시에 처리하기
캐싱을 통한 불필요한 작업 제거
- 캐시는 입력값과 결과값을 연결해 동일한 입력에 대한 작업을 한 번만 수행하도록 한다.
- 예를 들어
factorial(n)은 입력n에 따라 결과가 항상 같으므로functools.cache를 적용할 수 있다. - 빌드 캐싱이 올바르게 작동하려면 작업이 다음 조건을 만족해야 한다.
- Hermetic: 명시적으로 전달된 입력만 사용해 결과를 생성해야 한다.
- Idempotent: 동일한 입력에 대해 항상 동일한 결과를 내야 한다.
- 캐시 성능은 전체 호출 중 캐시에서 결과를 가져오는 비율인 캐시 적중률(hit rate)에 좌우된다.
작업 단위를 작게 나눠 캐시 적중률 높이기
- 이미지 목록 전체와 변환 목록 전체를 입력으로 받는
process_images()에 캐시를 적용하면, 이미지 하나만 추가돼도 전체 입력이 바뀐 것으로 간주된다. - 이 방식은 캐시 키가 지나치게 크고 거칠어, 작은 변경에도 모든 작업을 처음부터 다시 수행해야 한다.
- 대신 이미지 하나에 변환 하나를 적용하는
process_image(image, transform)처럼 더 작은 단위에 캐시를 적용할 수 있다. - 그러면 새로 추가되거나 변경된 이미지·변환 조합만 처리하고, 이미 계산한 조합은 캐시에서 재사용할 수 있다.
- 상위 수준 API는 유지하면서 내부 작업 단위만 세분화하는 방식이 성능과 재사용성을 높인다.
병렬화를 통한 작업 분산
- 이미지 처리처럼 서로 독립적인 작업은 여러 CPU 코어나 프로세스, 네트워크상의 다른 컴퓨팅 노드로 분산할 수 있다.
- 병렬 실행을 위해서는 다음 조건이 필요하다.
- 작업의 입력과 출력이 명확하게 정의되어야 한다.
- 입력과 출력을 스레드·프로세스·네트워크 경계 너머로 전달할 수 있어야 한다.
- 작업이 어떤 순서로 완료되거나 실패할지 보장할 수 없으므로 결과 처리 규칙을 명확히 해야 한다.
- 예시의 스레드 기반 구현은 이미지 결과가 입력 순서와 다르게 반환될 수 있다.
- 결과 순서 보장 여부는 API 계약의 일부이며, 사용자가 허용할 수 있는 동작인지 사전에 결정해야 한다.
병렬화에서도 작업 단위의 크기가 중요
- 작업 수가 적고 각각의 작업이 너무 크면 사용 가능한 컴퓨팅 자원에 충분히 분산하지 못한다.
- 반대로 작업을 지나치게 잘게 나누면 작업 전달·관리 오버헤드가 커질 수 있다.
- 적절한 작업 크기와 개수의 균형은 문제마다 다르므로 API와 빌드 단위를 설계할 때 함께 고려해야 한다.
- 캐싱과 병렬화 모두 입력·출력이 명확하고 독립적인 작은 작업 단위를 필요로 한다.
Bazel 빌드로 원칙 확장
- Bazel은 빌드를 방향성 비순환 그래프 형태의 타깃(target) 으로 정의한다.
- 각 타깃에는 다음 세 가지 핵심 요소가 있다.
- 빌드 단계의 입력이 되는 의존 파일
- 빌드 단계가 생성하는 출력 파일
- 입력을 출력으로 변환하는 명령어
- 이러한 명시적 정의를 바탕으로 변경된 타깃만 다시 빌드하고, 결과를 캐시하며, 서로 독립적인 타깃을 병렬 실행할 수 있다.
- 따라서 빌드 시스템의 성능 개선은 단순히 더 빠른 도구를 도입하는 문제가 아니라, 코드 성능 최적화와 마찬가지로 작업의 경계와 의존성을 설계하는 문제다.
빌드 시간을 줄이려면 먼저 의존성과 입출력을 명확히 정의하고, 변경 범위가 작은 작업 단위로 빌드를 나누는 것이 좋다. 그 위에 hermetic·idempotent한 작업을 캐시하고 독립 작업을 병렬화하면, 대규모 프로젝트에서도 불필요한 재빌드를 줄이고 개발자 피드백 속도를 높일 수 있다.
관련 글
큐레이션 요약을 이어서 읽어보세요.