parallelization

2 개의 포스트

slack4분 읽기큐레이션 요약

더 나은 소프트웨어를 만들기

빌드가 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한 작업을 캐시하고 독립 작업을 병렬화하면, 대규모 프로젝트에서도 불필요한 재빌드를 줄이고 개발자 피드백 속도를 높일 수 있다.

원문 읽기(새 탭에서 열림)
figma4분 읽기큐레이션 요약

Figma를 빠르게 유지하기

Figma는 2018년 한 대의 MacBook으로 운영하던 성능 테스트 체계가 제품과 조직의 성장으로 한계에 이르자 전면적인 개편을 추진했습니다. 플러그인, FigJam, Dev Mode 등 기능이 늘고 코드베이스가 복잡해지면서 기존의 소수 대형 파일 테스트만으로는 성능 회귀를 조기에 발견하기 어려워졌습니다. 이에 Figma는 모든 코드 변경을 대상으로 실제 하드웨어에서 병렬 성능 테스트를 실행하고, 10분 이내에 결과를 제공하는 확장 가능한 시스템을 목표로 삼았습니다. ## 한 대의 MacBook으로 시작한 성능 테스트 - 2018년 Figma는 한 대의 MacBook에서 동일한 테스트 시나리오를 반복 실행했습니다. - 테스트 결과와 실행 시간은 약 한 시간 간격으로 공유 대시보드에 기록됐습니다. - 당시에는 소수의 대형 디자인 파일만으로도 주요 성능 문제를 확인할 수 있었습니다. - 문서 렌더러 구조를 개선하고 WebAssembly 관련 버그를 해결하면서 Figma의 성능을 약 3배 향상시킨 사례도 있었습니다. - 작은 조직에서 단일 컴퓨터로 테스트하는 방식은 단순하고 비용이 낮다는 장점이 있었습니다. ## 제품과 조직의 성장으로 드러난 한계 - 5년 동안 코드베이스가 커지고 다음과 같은 기능이 추가됐습니다. - 플러그인 - Community 기능 - FigJam - Dev Mode - 수많은 제품 업데이트 - 기존에 사용하던 몇 개의 대형 디자인 파일은 늘어나는 기능과 예외 상황을 충분히 대표하지 못했습니다. - 기능별로 세밀한 성능 테스트를 작성하는 것이 이상적이었지만, 엔지니어와 매니저가 400명 이상으로 늘면서 모든 변경 사항을 한 사람이 추적하기 어려워졌습니다. - 성능 테스트 대상과 코드 변경이 많아지면서 단일 노트북만으로는 출시 전 성능 회귀를 안정적으로 발견할 수 없었습니다. - 원격 근무가 시작된 뒤에도 사무실에 있던 MacBook은 계속 테스트를 실행했고, 결국 2020년 10월 과열됐습니다. - 다른 노트북으로 같은 환경을 재현하려 했지만 테스트가 원활하게 실행되지 않아 새로운 시스템이 필요해졌습니다. ## 세밀한 성능 테스트의 필요성 - **세밀한 성능 테스트(granular performance test)**는 특정 기능이나 사용 패턴을 대규모 조건에서 검증하는 테스트입니다. - 예를 들어 Figma는 다음과 같은 상황을 시뮬레이션할 수 있습니다. - 100명의 협업 편집자가 동시에 파일을 편집 - 여러 사용자가 레이어를 이동 - 동시에 새로운 텍스트 입력 - 사용자가 빠르게 화면을 패닝 - 이런 테스트는 특정 기능의 성능 영향을 정확하게 파악하는 데 유용합니다. - 하지만 기능 수와 엣지 케이스가 계속 증가하면 모든 기능을 수동으로 테스트하는 방식은 조직 규모에 맞게 확장되지 않습니다. ## 새 성능 테스트 시스템의 목표 - Figma는 시스템을 처음부터 다시 설계하며 세 가지 문제를 해결하려 했습니다. - 성능에 영향을 줄 수 있는 기능의 증가 - 테스트 하드웨어 운영의 어려움 - 신뢰할 수 있는 성능 지표의 부족 - 메인 모노레포에 제출되는 **모든 코드 변경**을 테스트해 성능 회귀를 개발 초기에 발견하는 것을 목표로 삼았습니다. - 사용자가 버그를 보고한 뒤 대응하는 대신, 기능이 배포되기 전에 성능 문제를 예방하려 했습니다. - Figma 사용자는 하루에도 여러 시간 제품을 사용하기 때문에 작은 지연도 작업 흐름에 큰 영향을 줄 수 있다고 판단했습니다. - 성능을 기능 개발 이후의 사후 대응이 아니라 개발 과정에 포함되는 품질 기준으로 다루려 했습니다. ## 병렬 실행과 10분 성능 가드레일 - 테스트 대기 시간을 줄이기 위해 여러 테스트를 동시에 실행하는 **병렬 실행(parallel runs)**을 핵심 전략으로 채택했습니다. - 기존 CI에서도 클라우드 러너를 이용한 병렬 테스트를 이미 활용하고 있었습니다. - 성능 테스트 역시 수십 개의 스트레스 시나리오를 동시에 실행해야 목표 시간을 달성할 수 있었습니다. - 성능 가드레일 검사는 개발 흐름을 방해하지 않도록 **10분 이내**에 완료되어야 한다는 기준을 세웠습니다. - 모든 풀 리퀘스트를 실제 하드웨어에서 테스트하려면 피크 시점에 동일한 성능의 테스트 러너 약 100대가 필요했습니다. - 따라서 새로운 체계는 단순히 테스트 수를 늘리는 것이 아니라, 하드웨어를 효율적으로 운영하고 결과를 빠르게 수집하는 구조여야 했습니다. ## 실용적인 결론 성능 테스트는 제품과 조직이 작을 때는 단일 장비와 소수의 대표 시나리오만으로도 충분할 수 있지만, 기능·코드·팀 규모가 커지면 자동화와 병렬화가 필수입니다. 특히 사용자 경험에 직접 영향을 주는 성능은 출시 후 모니터링하는 것보다 모든 코드 변경 단계에서 회귀를 차단하는 가드레일로 운영하는 편이 효과적입니다.

원문 읽기(새 탭에서 열림)