Linear CI 최적화: AI 기반 개발의 성능 저하 문제 해결

AI 기반 코드 생성 에이전트는 코드 배포 속도를 지수적으로 증가시켰지만, 검증 파이프라인은 그에 못 미쳤습니다. Linear에서는 이러한 격차로 인해 지속적 통합(CI)이 주요 성능 저하 지점이 되었고, 인프라 비용이 증가하며 개발자와 에이전트 모두에게 피드백 지연이 발생했습니다. 다층 최적화 전략을 도입함으로써 Linear는 테스트 세트 크기가 올해 초 대비 거의 4배로 증가한 상황에서도, 풀 리퀘스트 대기 시간을 6분 이상에서 5분 이상으로 단축시켰습니다.

인프라 및 도구 업그레이드

기반 하드웨어와 컴파일러 툴체인을 업그레이드하면 CI 파이프라인 로직을 변경하지 않아도 즉각적인 성능 향상이 가능합니다.

  • 제3자 러너: GitHub Actions에서 더 빠른 CPU, 고성능 스토리지, 우수한 캐시 인프라를 갖춘 제3자 러너로 작업 부하를 이전함으로써, 작업 전체 평균 34%의 속도 향상이 발생했으며, tsc(TypeScript 컴파일러)와 같은 일부 작업은 52% 향상되었습니다.
  • 네이티브 컴파일: tsgo라는 네이티브 TypeScript 컴파일러로 전환함으로써 tsc 체크의 주간 중앙값이 73% 감소했고, 타입 체크가 성능 저하 지점에서 제거되었습니다.
  • AST 기반 린팅: Linear는 커스텀 린팅 규칙을 TypeScript 타입 정보에 의존하는 대신 추상 구문 트리(AST)를 기반으로 한 정적 분석으로 재작성했습니다. 이로 인해 ESLint가 전체 타입 그래프 없이도 작동할 수 있게 되어 API 린팅 시간은 68% 감소하고 전체 저장소 린팅 시간은 55% 감소했습니다.
  • Oxlint 통합: Oxlint로 전환함으로써 구문 기반 규칙 처리 효율성이 높아져 린팅에 소요되는 총 러너 분이 감소했습니다.

게이팅 작업 최적화

비critical path에 위치한 작업—즉, 다른 모든 작업이 시작되기 전에 완료되어야 하는 작업—은 전체 CI 지속 시간에 비례하지 않는 큰 영향을 미칩니다.

효율적인 변경 감지

PR의 차이점 기반으로 실행할 테스트를 결정하는 초기 작업을 최적화했습니다:

  • 제한된 가져오기 깊이: 변경 감지 작업의 가져오기 깊이를 제한함으로써 가장 느린 게이트가 94초에서 20초로 감소했습니다.
  • 워킹 트리 제거: 전체 워킹 트리가 필요 없는 작업에서는 체크아웃을 완전히 제거하여 실행 시간을 27초에서 7초로 단축했습니다.
  • 스파스 체크아웃: 커밋 푸시 및 머지 큐 이벤트에 대해 제한된 이력과 블롭 없는 스파스 체크아웃을 사용함으로써 추가로 11초를 절약했습니다.

이러한 변경으로 변경 감지 작업의 중앙값 지속 시간이 26초에서 8초로 감소했습니다.

회복성 및 경로 최소화

제3자 러너와 GitHub 사이의 네트워크 불안정성 문제를 해결하기 위해 actions/checkout을 대체하여 재시도 및 백오프를 구현한 커스텀 복합 액션을 도입했습니다. 또한 GIT_HTTP_LOW_SPEED_LIMIT와 GIT_HTTP_LOW_SPEED_TIME를 설정하여 정지 현상을 방지했습니다. 또한, 캐시 마커 작성과 같은 비필수 작업을 핵심 머지 경로에서 분리함으로써 API 풀 리퀘스트당 42초를 절약했습니다.

반복적인 설정 오버헤드 감소

설정 비용—러너 부팅, 패키지 설치, 종속성 프로비저닝—은 짧은 수명의 작업에서는 실제 테스트보다 더 많은 시간을 소비하는 경우가 많습니다.

  • 커스텀 CI 이미지: Linear는 공유 종속성(예: Postgres 클라이언트)을 기본 CI 이미지에 포함시켜, 각 샤드당 7~8초의 apt 설치 시간을 제거하고 네이티브 빌드 헤더 다운로드 중 발생하는 간헐적인 정지 문제를 방지했습니다.
  • 필터링된 종속성 설치: pnpm 모노레포에서 API 테스트 워크플로우를 전체 워크스페이스가 아닌 API 패키지와 그 종속성만 설치하도록 변경함으로써 pnpm install 시간을 4473초에서 1618초로 단축했습니다.
  • 재빌드 vs 캐싱: 테스트 결과, 필터링된 설치로 node_modules 재빌드(7.5초)가 캐시 복원(28초)보다 빠르다는 것이 확인되어 Linear는 node_modules 캐싱을 포기했습니다.
  • 스키마 스냅샷: 매번 전체 데이터베이스 마이그레이션 이력을 재실행하는 대신 API 컨테이너는 생성된 스키마 스냅샷과 부트스트랩 파일을 로드하여 데이터베이스 설정 시간을 12초에서 1~2초로 단축했습니다.
  • 작업 배치: 독립적인 7개의 짧은 체크를 두 개의 작업으로 통합하여 동시 실행을 가능하게 했습니다. 이로 인해 설정 오버헤드의 빈도가 감소하고 월간 약 87,000 러너-분(전체 CI 사용량의 11.8%)을 절약했습니다.

테스트 실행 효율성 향상

설정 비용을 최소화한 후, Linear는 워크플로우에서 가장 크고 자주 실행되는 API 세트를 적극적으로 병렬화했습니다.

작업 부하 균형

Vitest는 테스트 지속 시간이 아닌 파일 기반으로 작업을 분배하기 때문에, 몇몇 큰 파일이 샤드를 막을 수 있습니다. Linear는 이러한 큰 파일을 더 작고 집중적인 파일로 분할하고 샤드 수를 4개에서 8개로 늘렸습니다. 이로 인해 가장 느린 샤드의 지속 시간이 5.25분에서 4.33분으로 감소했습니다.

모듈 상태 캐싱

isolate: false를 사용하는 선택적 Vitest 프로젝트를 도입함으로써, 안전한 테스트 파일들이 각 워커 내에서 모듈 레지스트리를 공유할 수 있게 되었습니다. 이로 인해 매 파일마다 엔티티, GraphQL, 데코레이터 그래프를 중복으로 재빌드하는 일이 사라졌습니다. 이는 가장 큰 단일 개선 사례였으며, 가장 느린 샤드의 지속 시간을 약 300~379초에서 195초로 줄였고, 전체 API 샤드 러너 시간을 1회 실행당 32.8분에서 22분으로 감소시켰습니다.

커뮤니티의 시각과 반론

Linear의 기술적 접근은 인프라와 파이프라인 효율성에 초점을 맞추었지만, 커뮤니티 논의에서는 더 넓은 체계적 문제를 지적했습니다:

  • AI 테스트의 가치: 일부 기여자는 테스트 세트가 4배로 증가한 것이 비례하는 가치 증가를 반영하는지 의문을 제기하며, LLM이 많은 반복적 또는 단순한 테스트를 생성할 수 있다고 지적했습니다.
  • 시프트-레프트 검증: 린팅과 단위 테스트를 에이전트의 로컬 루프(훅 또는 스킬을 통해)로 이동하여 CI는 자원 집약적인 작업만 처리하고 사전 검증된 후보만 받도록 하는 제안이 있었습니다.
  • 대안 도구: 여러 개발자는 Bazel 또는 Grog과 같은 빌드 시스템을 지지하며, 강력한 캐싱과 종속성 그래프를 통해 거대한 프로젝트에서도 빌드 시간을 약 10초 내외로 유지할 수 있다고 주장했습니다.
  • 움직이는 성능 저하 지점: 엔지니어들 사이에서 흔한 견해는 CI를 빠르게 해도 성능 저하 지점이 인간 코드 리뷰, 배포, 롤백 프로세스로 이동할 뿐이라는 것입니다.

"CI를 빠르게 해도 성능 저하 지점은 리뷰와 배포로 옮겨질 뿐입니다. 에이전트는 대기열을 더 시끄럽게 만들었을 뿐, 그 자체를 만들지는 않았습니다."

Sources

관련