IT/CI CD

GitHub Actions concurrency 그룹과 matrix, 무심코 쓰면 서로를 죽인다

gfrog 2026. 6. 17. 09:41
SMALL

이거 모르는 분 꽤 많더라. 우리 팀도 작년 말까지 모르고 있었고, 우연히 디플로이 파이프라인 디버깅하다가 발견했다.

concurrency: 블록을 workflow 레벨에 걸어 둔 상태에서 matrix strategy로 OS 3개를 동시에 돌리면 어떻게 될까. 정답은 3개가 서로를 취소시킨다.

name: test
on: [push]
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - run: echo "${{ matrix.os }}"

워크플로 레벨 concurrency는 같은 그룹의 다른 워크플로 run을 취소하는 용도다. 같은 run 안의 matrix job 끼리는 영향이 없다. 여기까진 직관과 맞다.

문제는 job 레벨에 concurrency를 잘못 거는 경우다.

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    concurrency:
      group: deploy-${{ github.ref }}  # matrix 변수 안 들어감
      cancel-in-progress: true
    runs-on: ${{ matrix.os }}

이렇게 하면 같은 run 안에서 ubuntu, macos, windows job이 전부 같은 그룹에 들어간다. 그리고 cancel-in-progress: true 이므로 먼저 시작한 놈을 다음에 시작한 놈이 취소시킨다. 매트릭스 3개 중 마지막에 스케줄된 1개만 살아남고 나머지는 cancelled로 끝난다. UI에서 보면 "왜 macOS만 돌고 나머지는 빨갛지?" 하게 된다.

해결은 단순하다. matrix 변수를 그룹 키에 넣어준다.

concurrency:
  group: deploy-${{ github.ref }}-${{ matrix.os }}
  cancel-in-progress: true

이러면 OS별로 그룹이 분리되니까 같은 run 안의 다른 job은 영향을 안 받고, 다음 push가 들어오면 OS별로 in-progress인 놈만 취소된다.

여담으로 작년 12월쯤부터 cancel-in-progress: false 대신 queue: max 같은 옵션이 들어왔는데, queue: maxcancel-in-progress: true를 같이 못 쓴다. 한쪽만 골라야 한다. 큐잉이 필요하면 cancel은 꺼야 한다.

오늘 알게 된 분이 있다면, 본인 deploy 파이프라인의 concurrency 설정 한번 다시 보시는 걸 추천한다. 다른 환경(dev/stage/prod) 배포가 한 워크플로에서 matrix로 돌면 거의 확실히 이 함정에 빠져 있다.

BIG