Stop Waiting for GitHub Actions: Make Your CI Faster Today
GitHub Actions를 기다리지 마세요: 지금 CI를 빠르게 만드는 방법
GitHub Actions를 빠르게 만드는 출발점은 하드웨어 교체가 아니라 불필요한 작업을 줄이는 일입니다. 글은 실행 시간과 첫 유용한 실패까지 걸리는 시간을 먼저 측정한 뒤, 오래된 실행 취소, 의존성 캐시, 작업 순서와 병렬화, 아티팩트 보관을 단계별로 점검합니다.
- 주제
AI 요약
작은 README 수정에도 전체 테스트와 빌드를 돌리는 CI는 코드 리뷰를 늦추고 개발자의 집중을 끊습니다. 이 글은 Node.js 프로젝트를 예로 들어, GitHub Actions에서 불필요한 작업을 줄이고 개발자에게 유용한 결과를 더 빨리 전달하는 방법을 설명합니다. 단, 빠른 실행만 좇기보다 정확성, 러너 사용량, 보안, 유지보수 비용을 함께 살펴야 한다고 강조합니다.
먼저 측정하고 병목을 찾습니다
특정 단계가 느려 보인다는 이유만으로 바로 최적화하지 않습니다. 대표적인 실행을 여러 차례 살펴 전체 소요 시간, 대기 시간, 각 작업 시간, 의존성 설치 시간, 캐시 적중·실패 여부, 취소된 실행 비율, 아티팩트 크기와 사용 여부, 작업 간 반복되는 일을 기록합니다. 예시로 든 기준 실행은 의존성 설치 52초, 린트와 타입 검사 24초, 테스트 104초, 빌드 47초, 아티팩트 업로드 18초로 총 245초입니다. 한 번의 실행은 패키지 저장소 지연이나 캐시가 없는 러너 탓에 달라질 수 있으므로 여러 실행을 비교해야 합니다.
전체 완료 시간만 보지 말고 ‘첫 유용한 실패까지 걸리는 시간’, 모든 필수 검사가 끝날 때까지의 시간, PR당 러너 사용량도 확인합니다. 예를 들어 포맷 오류를 테스트가 끝난 뒤 알리는 대신 먼저 알려주면, 성공한 실행의 총시간이 크게 줄지 않아도 개발자가 더 빨리 대응할 수 있습니다.
오래된 실행을 취소하고 설치를 재현 가능하게 만듭니다
같은 PR에 새 커밋이 올라오면 이전 커밋의 테스트 결과는 쓸모가 줄어듭니다. workflow 수준에서 concurrency 그룹을 설정하고 cancel-in-progress: true를 지정하면 같은 그룹의 기존 실행을 취소할 수 있습니다. workflow 이름과 브랜치 또는 PR 참조를 그룹에 넣으면 서로 다른 workflow나 브랜치가 잘못 같은 그룹을 쓰는 일을 막습니다. 다만 순서대로 끝나야 하는 배포, 데이터베이스 마이그레이션, 릴리스 게시, 되돌릴 수 없는 외부 작업에는 무조건 취소 정책을 적용하지 말아야 합니다.
actions/setup-node의 cache: npm 설정은 npm 다운로드 캐시를 사용합니다. 설치가 끝난 node_modules를 통째로 캐시하는 방식과 다릅니다. npm ci는 잠금 파일에 기록된 의존성 트리를 다시 구성하고, 캐시가 있으면 패키지 다운로드를 줄입니다. 캐시가 없어도 설치가 정상 작동해야 합니다. 잠금 파일이 저장소 루트가 아니라면 cache-dependency-path에 경로를 지정하고, 여러 잠금 파일을 쓰면 각각 명시합니다. 캐시를 켠 뒤에는 첫 실행의 캐시 생성, 같은 잠금 파일을 쓰는 후속 실행의 복원, 잠금 파일 변경 시 새 캐시 적용을 확인합니다.
CI에서는 npm install보다 npm ci를 권합니다. npm ci는 잠금 파일을 요구하고 기존 node_modules를 제거한 뒤 잠금 파일에 맞춰 설치합니다. package.json과 잠금 파일이 어긋나면 조용히 잠금 파일을 갱신하지 않고 실패하므로 저장소에 커밋한 의존성 상태를 검증하기 쉽습니다. Node.js 버전도 러너 이미지에 미리 설치된 값에 맡기지 말고 명시합니다.
빠른 검사와 비싼 작업의 순서를 설계합니다
린트와 타입 검사처럼 빠르고 진단 가치가 높은 검사를 테스트와 빌드보다 먼저 실행하면 문제를 일찍 알릴 수 있습니다. 작은 저장소라면 한 job에서 의존성을 한 번만 설치하고 검사를 순서대로 돌리는 편이 단순하고 효율적일 수 있습니다. 반면 독립적인 테스트와 빌드는 병렬 실행으로 벽시계 시간을 줄일 수 있습니다. 이때 job마다 저장소를 체크아웃하고 의존성을 다시 설치하므로 전체 러너 사용량은 늘어납니다.
글은 품질 검사 job을 먼저 실행하고, 성공한 뒤 테스트와 빌드를 병렬로 돌리는 구성을 예로 듭니다. 린트와 타입 검사가 자주 실패하고 테스트가 비싼 프로젝트라면 유용할 수 있지만, 작은 프로젝트에서는 여러 번의 npm ci 비용이 병렬화 이득보다 클 수 있습니다. 여러 Node.js 버전을 지원한다고 주장하는 프로젝트라면 matrix로 실제 지원 버전을 검사하되, 지원하지 않는 조합까지 무작정 늘리지 않습니다.
트리거, 아티팩트, 보안도 함께 점검합니다
문서만 바뀐 경우 전체 파이프라인을 건너뛰도록 path filter를 설정할 수 있습니다. 하지만 필수 workflow가 필터 때문에 실행되지 않으면 필수 체크가 대기 상태로 남아 PR 병합을 막을 수 있습니다. 공유 패키지, 루트 설정 파일, 생성 파일, workflow 자체가 영향을 받는지도 확인해야 합니다. 복잡한 monorepo에서는 항상 실행되는 분류 job이 필요한 작업을 판별하는 방식이 더 안전할 수 있습니다.
아티팩트는 다른 job이나 사람이 실제로 쓸 파일에만 남깁니다. 일반 PR에서 아무도 내려받지 않는 빌드는 매번 업로드하지 않고, 메인 브랜치에서만 올리거나 실패한 테스트의 진단 자료만 보관할 수 있습니다. 다시 만들 수 있는 의존성 다운로드 데이터에는 캐시를, 소비자가 확인하거나 사용할 결과물에는 아티팩트를 씁니다. 보관 기간도 목적에 맞춰 정합니다.
각 job에는 관측된 실행 시간에 여유를 더한 timeout을 둡니다. 저장소 내용만 읽는 workflow라면 contents: read처럼 최소 권한으로 시작하고, 필요한 job에만 권한을 추가합니다. 캐시에는 API 키, 토큰, 서명 키, 비밀 환경 파일을 넣지 않습니다. 신뢰할 수 없는 PR에 쓰기 가능한 캐시 권한을 부여하면 캐시 오염 위험이 생길 수 있으므로 이벤트별 접근 정책을 확인해야 합니다. 액션 버전 고정과 업데이트 정책도 조직의 보안 기준에 맞춥니다.
측정 결과로 다시 조정합니다
글이 제안하는 순서는 현재 실행 측정, 오래된 실행 취소, 의존성 다운로드 캐시, 재현 가능한 설치, 빠른 실패 우선 보고, 필요한 경우에만 병렬화, 신중한 작업 생략, 유용한 아티팩트만 보관입니다. 캐시 복원 자체가 대체하려는 작업보다 오래 걸릴 수 있고, 병렬 job은 러너 사용량을 늘립니다. 빠르게 끝나더라도 필요한 검사를 빠뜨린 workflow라면 개선이 아닙니다. YAML을 복잡하게 만드는 대신 실제 측정값을 바탕으로 신뢰할 수 있는 피드백을 빠르게 제공하는 데 초점을 둡니다.
dev.to 반응
- @nyaomaru — 좋은 글입니다, John! workflow를 더 ‘최적화된 것처럼’ 보이게 만드는 대신 불필요한 작업을 줄이는 데 집중한 점이 특히 좋았습니다. 먼저 측정하라는 조언, 특히 ‘첫 유용한 실패까지 걸리는 시간’이 인상적이었습니다. 캐싱, 경로 필터, 병렬 job에 단서를 붙인 점도 좋았습니다. 무턱대고 적용하면 최적화가 역효과를 내기 쉬운 부분이니까요. 이 글을 읽고 제 GitHub Actions workflow도 다시 살펴보고 싶어졌습니다. changelog-bot도 포함해서요. 정리해주셔서 감사합니다!
- @johnnylemonny — 정말 감사합니다! 제가 전하고 싶었던 점을 정확히 짚어주셨네요. workflow를 더 정교해 보이게 만들면서 실제로는 복잡성, 러너 사용량, 유지보수 비용을 늘리기는 놀라울 만큼 쉽습니다. ‘첫 유용한 실패까지 걸리는 시간’은 대시보드에서 보이는 총시간뿐 아니라 개발자가 workflow를 어떻게 느끼는지 보여줘서 제가 좋아하는 지표입니다. 캐싱, 경로 필터, 병렬화 예시는 처음에는 좋아 보였지만 예상하지 못한 부작용이 생기는 최적화를 직접 보거나 가끔 만들어본 경험에서 나왔습니다. changelog-bot을 다시 살펴보신 뒤 어떤 점을 발견하셨는지도 궁금하네요. 몇 달 만에 workflow를 새로 보면 불필요한 작업이 놀랄 만큼 많이 보일 때가 있습니다. 읽어주시고 프로젝트에 들인 좋은 작업도 공유해주셔서 다시 한번 감사합니다!
- @mridul_it_is — 정말 좋은 글입니다. 이제부터 제 파이프라인에도 적용해보겠습니다.
원문: dev.to / 번역·요약: Trawling