파이프라인을 6분으로 줄였는데 왜 배포 후 장애가 늘었나?

결론부터 적는다. 병렬화와 캐시로 벽시계 시간을 줄이는 것과, 그 파이프라인이 실제로 버그를 잡아내는 것은 별개 지표다. 우리 팀은 전체 파이프라인을 45분에서 6분대로 줄인 뒤 오히려 배포 후 장애 재현 빈도가 올라갔고, 원인은 속도 개선 과정에서 플레이키 테스트가 "빨리 재시도되며 통과하는" 방식으로 숨겨졌기 때문이었다.

  • 캐시·병렬화·변경 기반 실행은 각각 독립적으로 시간을 줄이지만, 효과를 합산해서 해석하면 안 된다. 같은 유형의 사례에서도 단계별로 32분→18분→12분→6분처럼 여러 기법이 겹쳐 작동한 결과였다 출처: scaleweaver.com.
  • 병렬 테스트는 벽시계 시간을 줄이지만 총 컴퓨트 사용량과 비용은 늘 수 있다 출처: oneuptime.com.
  • 캐시 키를 lockfile·OS와 연결하지 않으면 오래된 의존성이 재사용될 위험이 있다.
  • 플레이키 테스트는 병렬화로 "빨리 재현"될 뿐 신뢰성이 자동으로 올라가지 않는다.
  • 평균 파이프라인 시간만 보지 말고 p95 시간, 캐시 적중률, 재시도율을 함께 기록해야 한다.

장애 경위는 이랬다. Node.js 백엔드 프로젝트에서 Jest 테스트를 4개 샤드로 나누고, 의존성 설치에 cache: 'pip'류의 캐시 구성을 적용한 뒤 파이프라인은 확실히 빨라졌다. 그런데 2주 뒤부터 스테이징 배포 후 간헐적으로 500 에러가 보고됐고, 매번 같은 PR을 다시 열어 파이프라인을 돌리면 그린으로 통과했다. "로컬에서도 안 터지고 CI에서도 통과하는데 운영에서만 터진다"는 전형적인 패턴이었다.

원인은 캐시 오염인가?

1차 가설은 캐시 오염이었다. 의존성 캐시 키를 느슨하게 잡아 OS 버전이나 lockfile 변경을 제대로 반영하지 못하면, 오래된 패키지가 재사용되면서 런타임에서만 재현되는 버그가 생길 수 있다는 사례가 보고돼 있다 출처: techbuddies.io. 캐시 키를 확인해보니 os-${{ runner.os }} 수준까지만 포함돼 있었고 lockfile 해시는 빠져 있었다. 유력한 용의자였지만, 캐시를 강제로 무효화하고 다시 돌려도 같은 간헐적 실패가 재현됐다. 캐시만으로는 설명이 안 됐다.

샤드 불균형이 테스트를 누락시켰나?

2차 가설은 샤드 배치 문제였다. 4개 샤드로 나눌 때 테스트 개수 기준으로 균등 분배했는데, 실행 시간 기준이 아니었다. 병렬 테스트 가이드에서는 테스트 수가 아니라 실행시간 기준으로 나누지 않으면 가장 느린 샤드가 전체 피드백 시간을 좌우한다고 지적한다 출처: oneuptime.com. 샤드별 실행 시간을 찍어보니 한 샤드는 1분 40초, 다른 샤드는 3분 50초로 거의 2배 차이가 났다. 느린 샤드가 전체 시간을 늘렸을 뿐 테스트 누락과는 직접 관련이 없었다. 다만 이 조사 과정에서 중요한 단서를 하나 발견했다 — 특정 테스트가 샤드마다 다른 결과를 냈다.

플레이키 테스트가 재시도로 은폐됐나?

이게 실제 원인이었다. CI 설정에 실패한 테스트를 자동으로 2회까지 재시도하는 옵션이 들어가 있었고, 재시도 로그를 뒤져보니 특정 테스트 1개가 15~20%대 실패율로 흔들리고 있었다. 이 비율은 유사 사례에서 보고된 플레이키 테스트 실패율 범위와 비슷했다 출처: scaleweaver.com. 병렬화 이후 전체 테스트가 빠르게 끝나다 보니, 이 테스트가 1차 실패 후 재시도에서 통과하면 CI 결과는 그냥 녹색으로 찍혔다. 즉 병렬화는 플레이키 테스트를 "더 빨리" 재현하게 해줬을 뿐, 재시도 설정이 그 실패를 화면에서 지워버린 것이다.

로그로 원인을 어떻게 확정했나?

CI 로그에서 재시도 이벤트만 따로 필터링해 집계했다. 지난 30일 머지 기준으로 재시도가 1회 이상 발생한 PR 비율, 그중 재시도 성공으로 그린 처리된 비율, 그리고 운영 장애 발생 시점과 해당 PR 머지 시점을 겹쳐봤다. 재시도 성공으로 그린 처리된 PR 중 일부가 실제 운영 장애 배포와 시간대가 겹쳤다. 테스트 자체를 로컬에서 100회 반복 실행해보니 실패율이 비슷한 범위로 재현됐고, 실패 원인은 비동기 타이밍 의존성이 있는 테스트 코드였다. 캐시나 샤드 배치 자체는 증상을 가속시켰을 뿐 근본 원인이 아니었다는 결론을 여기서 확정했다.

무엇을 바꿨고 어떤 지표로 확인했나?

세 가지를 바꿨다. 첫째, 캐시 키에 lockfile 해시와 런타임 버전을 추가했다. 둘째, 샤드를 테스트 개수가 아니라 직전 10회 실행의 평균 소요시간 기준으로 재배치했다. 셋째, 가장 중요한 변경으로 자동 재시도를 "통과로 간주"하지 않고 재시도 발생 자체를 실패 원인 비율에 별도로 집계하도록 대시보드를 바꿨다. 이후 지표는 평균 파이프라인 시간 하나가 아니라 p95 시간, 캐시 적중률, 재시도율, 실패 원인별 비율을 함께 보는 방식으로 바꿨다. 해당 테스트는 격리해 별도 큐로 옮기고 수정 전까지 머지 블로커에서 제외했다. 변경 후 4주간 재시도율은 눈에 띄게 낮아졌고, 같은 증상의 운영 장애는 재발하지 않았다 — 다만 이는 우리 저장소의 관찰이며, 모든 환경에서 같은 폭으로 줄어든다고 보장할 수는 없다.

같은 함정을 피하려면 무엇을 기본값으로 삼아야 하나?

속도 개선 작업은 반드시 신뢰성 지표와 짝을 지어 봐야 한다는 게 핵심 교훈이다. 캐시는 lockfile·OS·런타임 버전을 키에 명시적으로 포함하고, 샤드는 테스트 수가 아니라 실행시간 기준으로 재배치하며, 자동 재시도는 편의 기능이 아니라 별도로 추적해야 할 리스크 신호로 취급하는 것을 기본값으로 삼았다. 2026년 기준으로 파이프라인 시간을 줄이는 기법 자체는 여러 사례에서 검증돼 있지만, 그 시간 단축이 테스트의 검증력을 그대로 유지하는지는 별도로 확인해야 하는 항목이라는 점을 이번 회고로 다시 확인했다.

핵심 정리

  • 파이프라인 속도와 테스트 신뢰성은 별개 지표이며, 하나가 좋아졌다고 다른 하나가 자동으로 좋아지지 않는다.
  • 캐시 오염 의심은 키에 lockfile·OS·런타임 버전이 빠져 있는지부터 확인한다.
  • 샤드는 테스트 개수가 아니라 실행시간 기준으로 나눠야 가장 느린 샤드가 전체 피드백 시간을 좌우하는 문제를 줄일 수 있다.
  • 자동 재시도는 플레이키 테스트를 고치지 않고 화면에서만 숨길 수 있으므로 재시도율을 별도 지표로 추적해야 한다.
  • 평균 파이프라인 시간 대신 p95 시간, 캐시 적중률, 재시도율, 실패 원인별 비율을 함께 보는 구성이 안전하다.