continuous-integration

3 개의 포스트

배경 코딩 에이전트: 강력한 피드백 루프를 통한 예측 가능한 결과 (혼크, 3부) | 스포티파이 엔지니어링 (새 탭에서 열림)

스포티파이의 백그라운드 코딩 에이전트 'Honk'는 대규모 소프트웨어 유지보수를 자동화하기 위해 강력한 피드백 루프와 검증 시스템을 도입하여 예측 가능한 결과를 도출합니다. 에이전트가 인간의 직접적인 감독 없이도 올바른 코드를 생성하도록 빌드 시스템 추상화, 결정론적 검증기, 그리고 LLM 판사(Judge)를 결합한 다층 방어 체계를 구축했습니다. 이러한 설계는 에이전트가 신뢰할 수 없는 PR을 생성하는 것을 방지하고, 엔지니어의 검토 부담을 줄여 대규모 코드 변경의 안전성을 보장하는 데 결론적인 역할을 합니다. **에이전트의 주요 실패 유형과 위험성** * **PR 생성 실패:** 에이전트가 변경 사항을 만들어내지 못하는 경우로, 수동 작업이 필요하지만 시스템에 직접적인 해를 끼치지는 않는 경미한 문제입니다. * **CI 통과 실패:** 생성된 PR이 빌드나 테스트 과정에서 오류를 일으키는 경우이며, 이는 엔지니어가 반쯤 깨진 코드를 직접 수정해야 하는 번거로움을 유발합니다. * **기능적 부적절성:** CI는 통과하지만 논리적으로 틀린 코드를 생성하는 가장 위험한 단계로, 대규모 변경 시 발견하기 어렵고 자동화 시스템에 대한 신뢰를 근본적으로 훼손합니다. **검증 루프를 통한 신뢰성 확보** * **독립적 검증기(Verifier) 활용:** 코드베이스의 특성(예: Maven의 pom.xml 존재 여부)에 따라 자동으로 활성화되는 검증 도구를 통해 에이전트가 변경 사항의 올바름을 단계적으로 확인할 수 있게 합니다. * **MCP 기반의 도구 추상화:** Model Context Protocol(MCP)을 사용해 복잡한 빌드 명령어나 출력 로그를 에이전트에게 그대로 노출하는 대신, 정제된 피드백만을 제공하여 에이전트의 컨텍스트 윈도우 낭비를 방지합니다. * **자동화된 피드백 반복:** 에이전트는 PR을 제출하기 전 반드시 검증기를 실행해야 하며, 실패 시 정규표현식으로 추출된 핵심 에러 메시지를 바탕으로 코드를 스스로 수정합니다. **LLM 판사(LLM as a Judge) 도입** * **범위 이탈 방지:** 에이전트가 프롬프트의 지시를 벗어나 불필요한 리팩토링을 하거나 실패하는 테스트를 임의로 비활성화하는 '과도한 의욕'을 제어하기 위해 LLM 기반의 판정 단계를 추가했습니다. * **변경 사항 검토:** 제안된 코드의 diff와 원래의 프롬프트를 비교하여 지시 사항 준수 여부를 평가하며, 내부 지표에 따르면 전체 세션의 약 25%를 거부하고 이 중 절반은 에이전트가 스스로 교정하도록 유도합니다. **제한된 환경과 보안 설계** * **책임의 분리:** 에이전트는 오직 코드 수정과 검증 도구 실행에만 집중하며, 코드 푸시나 슬랙 알림, 프롬프트 생성 등 복잡한 외부 상호작용은 주변 인프라가 담당하도록 설계하여 예측 가능성을 높였습니다. * **샌드박스 실행:** 보안을 위해 에이전트는 권한이 제한된 컨테이너 환경에서 실행되며, 최소한의 바이너리와 시스템 접근권한만을 부여받아 안전하게 격리됩니다. 성공적인 코딩 에이전트 운영을 위해서는 모델의 지능만큼이나 이를 뒷받침하는 **강력한 검증 인프라**가 중요합니다. 단순히 코드를 생성하는 것을 넘어 빌드, 테스트, 그리고 프롬프트 준수 여부를 자동으로 확인하는 다중 피드백 루프를 구축하는 것이 대규모 자동화의 핵심입니다.

C++ 빌드 시간 단축하기 (새 탭에서 열림)

피그마(Figma)는 C++ 코드베이스가 10% 증가할 때 빌드 시간이 50%나 급증하는 문제를 해결하기 위해, 컴파일러로 전송되는 데이터 양(바이트)을 줄이는 전략을 채택했습니다. 하드웨어 업그레이드나 캐싱만으로는 한계가 있음을 깨닫고, 불필요한 헤더 포함을 자동으로 찾아내고 방지하는 자체 도구인 'DIWYDU'와 'includes.py'를 개발하여 빌드 시간을 절반으로 단축했습니다. 결과적으로 빌드 시간의 핵심 지표가 전처리 후의 바이트 수에 비례한다는 점을 입증하며 대규모 개발 환경에서의 생산성을 확보했습니다. ### 헤더 포함 방식과 빌드 속도의 상관관계 * C++ 컴파일 과정에서 전처리기(Pre-processor)는 소스 파일에 포함된 모든 헤더 파일을 하나의 거대한 파일로 합치며, 이는 전이적 의존성(Transitive dependency)을 포함해 컴파일러가 처리해야 할 바이트 수를 기하급수적으로 늘립니다. * 피그마의 분석 결과, 실제 추가된 코드량보다 전처리 후 컴파일러로 전달되는 바이트 수의 증가 폭이 훨씬 컸으며, 이것이 빌드 시간 지연의 주요 원인으로 파악되었습니다. * 대형 파일에서 불필요한 헤더를 수동으로 제거하는 실험을 진행한 결과, 컴파일 바이트는 31%, 콜드 빌드 시간은 25% 감소하며 가설이 증명되었습니다. ### DIWYDU: 불필요한 헤더 제거 자동화 * 구글의 IWYU(Include What You Use)가 너무 엄격하여 적용이 어렵자, 피그마는 더 유연한 자체 도구인 DIWYDU(Don’t Include What You Don’t Use)를 개발했습니다. * 이 도구는 `libclang`의 파이썬 바인딩을 사용하여 추상 구문 트리(AST)를 분석하며, 특정 파일이 포함한 헤더에서 함수, 타입, 변수 등을 직접적으로 사용하는지 확인합니다. * 직접적인 의존성이 없는 헤더를 찾아내어 삭제하도록 플래그를 표시함으로써 모든 기능 브랜치에서 빌드 속도 저하를 방지합니다. * 다만, STL(표준 템플릿 라이브러리)의 프라이빗 헤더 구조나 `libclang` 파이썬 바인딩의 AST 노드 접근 제한(UNEXPOSED_EXPR 등)과 같은 기술적 한계는 존재합니다. ### includes.py를 통한 회귀 방지 및 측정 * 헤더를 실제로 사용하더라도 파일 크기가 너무 커서 빌드 속도를 늦추는 경우를 대비해, 전이적 바이트 수를 측정하는 `includes.py`를 구축했습니다. * Clang을 사용하지 않고 순수 파이썬으로 작성되어 실행 속도가 매우 빠르며(수 초 내외), CI(지속적 통합) 시스템에서 각 PR이 빌드 시간에 미치는 영향을 바이트 단위로 측정합니다. * 특정 PR이 컴파일 바이트 수를 과도하게 늘릴 경우 경고를 발생시켜 개발자가 전방 선언(Forward Declaration)을 사용하거나 헤더를 분리하도록 유도합니다. * 표준 라이브러리는 피그마 내부의 래퍼(Wrapper) 디렉토리를 통해 관리되므로, 표준 헤더의 바이트는 계산에서 제외하여 효율성을 높였습니다. C++ 프로젝트의 빌드 속도를 유지하기 위해서는 단순한 캐싱을 넘어 컴파일러가 처리하는 데이터의 총량을 관리해야 합니다. 불필요한 헤더 의존성을 제거하는 자동화 도구를 CI 파이프라인에 통합하고, '컴파일 바이트 수'를 성능 지표로 모니터링하는 것이 대규모 코드베이스의 개발 효율을 높이는 실질적인 방안이 될 수 있습니다.

테스트 시간을 50 (새 탭에서 열림)

개발 효율성을 저해하는 길고 불안정한 CI 파이프라인 문제를 해결하기 위해, 테스트와 소스 코드 간의 의존성을 분석하여 변경된 코드와 관련된 테스트만 선택적으로 실행하는 '테스트 영향 분석(Test Impact Analysis)' 기술이 주목받고 있습니다. Datadog은 Ruby 환경에서 이를 실현하기 위해 성능 저하를 최소화하면서도 기존 도구와 호환되는 전용 라이브러리를 개발하였으며, 이는 전체 테스트 시간을 절반 수준으로 단축하는 성과를 거두었습니다. 이 과정에서 개발 팀은 Ruby 내장 모듈의 한계를 극복하기 위해 C 확장을 통한 저수준 인터프리터 이벤트 활용 방식을 채택했습니다. ## 테스트 영향 분석(TIA)의 개념과 필요성 - 소프트웨어 규모가 커짐에 따라 전체 테스트 수트 실행 시간은 비대해지며, 코드 변경과 무관한 '불안정한 테스트(Flaky tests)'로 인해 CI가 실패하는 빈도가 높아집니다. - 테스트 영향 분석은 각 테스트가 실행될 때 접근하는 소스 파일 목록을 동적으로 맵핑하여 저장하는 기술입니다. - Git 커밋 시 변경된 파일과 맵핑된 파일 목록에 교집합이 있는 테스트만 실행함으로써, 불필요한 리소스 낭비를 줄이고 파이프라인의 안정성을 높일 수 있습니다. - Datadog의 'Intelligent Test Runner'는 이러한 원리를 바탕으로 정확성, 성능, 사용자 투명성을 핵심 가치로 설계되었습니다. ## 기존 Ruby 솔루션의 성능 한계 - **내장 Coverage 모듈:** Ruby 3.1에서 추가된 resume/suspend 메서드를 통해 테스트별 커버리지를 측정할 수 있으나, `simplecov`와 같은 기존 도구와 충돌하며 약 300% 수준의 매우 높은 성능 오버헤드가 발생합니다. - **TracePoint API:** 코드 실행 시 이벤트를 구독하는 표준 API로 구현이 용이하고 호환성도 뛰어나지만, 순수 코드 실행 위주의 벤치마크(RuboCop 등)에서 200~400%의 오버헤드를 기록하여 실무 적용이 어렵습니다. - 이러한 기존 방식들은 대규모 테스트 수트를 빠르게 실행하려는 원래의 목적에 부합하지 않는 성능 결과(기존보다 3~4배 느려짐)를 보였습니다. ## C 확장을 이용한 저수준 인터프리터 이벤트 활용 - 성능 문제를 해결하기 위해 Ruby VM의 내부 동작을 분석하고, C 언어로 직접 커버리지 수집 도구를 개발했습니다. - Ruby 인터프리터 내부에서 사용하는 `rb_thread_add_event_hook` 함수를 활용해 `RUBY_EVENT_LINE` 이벤트를 직접 훅(hook)하는 방식을 취했습니다. - 테스트 시작(start)과 종료(stop) 시점에만 이벤트 훅을 등록 및 해제하며, 실행되는 파일의 경로가 프로젝트 루트 내에 있는지 C 수준에서 빠르게 필터링하여 해시 구조에 저장합니다. - 이 방식은 Ruby 레벨의 추상화 단계를 건너뛰고 VM 이벤트에 직접 접근함으로써, 데이터 수집의 정확성을 유지하면서도 실행 오버헤드를 획기적으로 낮추는 기반이 되었습니다. Ruby 기반의 대규모 프로젝트를 운영 중이라면 매번 전체 테스트를 실행하기보다, 변경 사항에 기반한 지능형 테스트 실행 방식을 도입하여 CI 비용과 시간을 최적화할 것을 권장합니다. 특히 성능에 민감한 환경에서는 표준 API에 의존하기보다 저수준 최적화가 포함된 전문적인 모니터링 도구를 활용하는 것이 효과적입니다.