LabHub

GitLab CI/CD · 실행 모델 — 순서·조건·전달 · 이론

스테이지의 줄서기와 needs 의 DAG

LabHub 에서 이어서 보기

한 줄 요약

stages 는 잡을 칸으로 줄 세워 앞 칸이 다 끝나야 다음 칸이 출발하게 만들고, needs 는 그 칸 벽을 무시하고 "내가 실제로 기다려야 하는 것"만 지정해 파이프라인을 DAG 로 바꾼다.

왜 이게 필요했나

스테이지 모델은 이해하기 쉽다는 큰 장점이 있다. build 가 다 끝나면 test 가 시작하고, test 가 다 끝나면 deploy 가 시작한다. 문제는 이 단순함이 곧 낭비가 된다는 것이다.

test 스테이지에 잡이 다섯 개 있는데 그중 하나가 12분짜리 통합 테스트라고 하자. 나머지 넷은 1분이면 끝나지만 deploy 스테이지의 잡은 12분을 통째로 기다린다. 실제로는 배포가 통합 테스트 결과만 필요할 수도 있는데, 스테이지 모델은 그 사실을 표현할 방법이 없다. 잡 사이의 진짜 의존 관계는 칸보다 훨씬 성기고, 칸은 그 성긴 관계를 빽빽한 것으로 반올림한다.

더 답답한 경우는 반대편이다. 소스만 보면 되는 lint 잡이 build 스테이지 뒤에 있다는 이유로 빌드가 끝날 때까지 시작도 못 한다. 개발자는 오타 하나를 알기 위해 빌드 시간을 기다린다. 파이프라인 전체 시간이 15분을 넘으면 사람들이 결과를 기다리지 않고 다른 일을 하러 가면서 피드백 루프가 무너지는데, 이런 헛기다림이 그 15분의 큰 몫이다.

어떻게 동작하나

needs 를 잡에 적으면 그 잡은 스테이지 순서를 무시하고 나열된 잡들만 기다린다. 이때 세 가지 규칙이 함께 작동한다.

첫째, needs 를 아예 적지 않은 잡은 예전 그대로다. 자기 앞 스테이지에 있는 모든 잡이 끝나야 출발한다. 그래서 한 파일 안에서 스테이지 방식과 DAG 방식이 섞여도 된다.

둘째, needs: [] 는 "아무것도 기다리지 않는다"는 뜻이다. 키가 없는 것과 빈 목록은 정반대의 의미를 갖는다. 이 한 줄이 lint 잡을 파이프라인 맨 앞으로 끌어온다.

셋째, needs 는 앞 스테이지만 가리킬 수 있는 게 아니다. 같은 스테이지의 잡도 가리킬 수 있고, 그러면 같은 칸 안에서도 순서가 생긴다. 반대로 뒤 스테이지의 잡을 가리키면 순환이 되어 GitLab 이 설정을 거부한다.

이 세 규칙을 합치면 파이프라인은 방향 있는 비순환 그래프가 되고, 실행 순서는 그래프의 위상 정렬로 결정된다. 실행 계획을 눈으로 보고 싶다면 웨이브로 세면 된다. 기다릴 것이 하나도 없는 잡들이 첫 웨이브에 함께 출발하고, 그들이 끝나야 비로소 출발할 수 있는 잡들이 두 번째 웨이브가 된다. 웨이브 수가 그 파이프라인의 최소 깊이이고, 그것이 아무리 러너를 늘려도 줄지 않는 시간의 하한이다.

한 가지 실무 제약이 있다. 한 잡이 걸 수 있는 needs 개수에는 상한이 있고(기본 50), 그래서 잡 수백 개짜리 파이프라인을 완전한 DAG 로 만들려는 시도는 대개 중간에서 멈춘다. 그럴 때는 병목이 되는 몇 개만 골라 needs 를 걸고 나머지는 스테이지에 맡기는 편이 낫다.

현장에서 만나는 모습

DAG 로 바꾼 팀이 가장 먼저 겪는 놀라움은 시간이 줄어드는 것이 아니라 의존 관계가 문서화된다는 점이다. needs 를 적으려면 이 잡이 무엇 때문에 기다리는지 답해야 하는데, 막상 적으려고 보면 아무도 이유를 모르는 기다림이 여럿 나온다. 그 기다림은 대부분 지울 수 있는 것이었다.

반대 방향의 사고도 있다. needs 로 앞당긴 잡이 앞 잡의 산출물을 쓰고 있었는데, 그 의존을 needs 에 적지 않아 파일이 없다며 실패한다. 이 실패는 러너가 비어 있을 때는 재현되지 않고 바쁠 때만 나타나기도 해서, 플레이키 테스트로 오해받기 쉽다.

이어서 볼 것

같은 파이프라인이라도 브랜치에 따라, 병합 요청인지에 따라 다른 잡이 만들어져야 한다. 그 선택을 담당하는 rules 를 다음 글에서 본다.