CI 병렬화는 왜 `&`가 아니라 수명 주기 계약이어야 할까
GitHub Actions의 새 background, wait, cancel, parallel 문법은 단순 속도 개선이 아니라 어떤 운영 계약을 만들까?
CI 병렬화는 왜 &가 아니라 수명 주기 계약이어야 할까
- 카테고리: infra_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: GitHub Actions의 새
background,wait,cancel,parallel문법은 단순 속도 개선이 아니라 어떤 운영 계약을 만들까? - 핵심 출처:
- Actions steps can now be run in parallel - 2026-06-25, 확인일 2026-07-12
- Workflow syntax for GitHub Actions - 확인일 2026-07-12
- actions/runner #4476 Background steps execution engine - 2026-06-07 merged, 확인일 2026-07-12
- actions/runner #4479 Add background step deferral infrastructure and metadata plumbing - 2026-06-04 merged, 확인일 2026-07-12
- Control the concurrency of workflows and jobs - 확인일 2026-07-12
1. 왜 지금 봐야 하나
CI가 느릴 때 가장 쉬운 해결책은 “병렬로 돌리자”다. 그런데 GitHub Actions 한 job 안에서 여러 일을 동시에 돌리려면 지금까지는 보통 shell의 &를 썼다.
- run: |
npm run api &
npm run web &
wait
작동은 한다. 하지만 운영 관점에서는 애매하다. 어떤 프로세스가 실패했는지 로그가 섞이고, 언제 기다려야 하는지 YAML이 아니라 shell 스크립트에 숨어 있고, job 종료 전 cleanup 시점에 살아 있는 프로세스를 누가 정리하는지도 흐릿해진다.
GitHub는 2026년 6월 25일 changelog에서 Actions step을 한 job 안에서 병렬 실행할 수 있는 새 문법을 발표했다. 핵심 키워드는 네 가지다.
background: true: step을 비동기로 시작하고 다음 step으로 넘어간다.wait/wait-all: 특정 background step 또는 모든 background step이 끝날 때까지 기다린다.cancel: 더 이상 필요 없는 background step을 정상 종료한다.parallel: 여러 step을 background로 실행한 뒤 자동으로 기다리는 축약 문법이다.
이 글의 핵심은 “CI가 빨라졌다”가 아니다. 병렬화는 속도 최적화가 아니라 작업의 수명 주기, 실패 전파, 로그, 비용, 공유 리소스 순서를 명시하는 운영 계약이라는 점이다.
2. 핵심 개념
GitHub Actions에는 이미 여러 수준의 병렬성이 있었다.
첫째, workflow 안의 job은 기본적으로 병렬 실행된다. needs를 써야 순서가 생긴다.
둘째, matrix strategy로 여러 OS, Node 버전, 패키지 조합을 동시에 테스트할 수 있다.
셋째, workflow 또는 job 수준의 concurrency로 같은 배포 대상, 같은 branch, 같은 환경에 대해 “동시에 하나만 실행”하도록 제한할 수 있다. GitHub 문서에 따르면 concurrency group 안에는 최대 하나의 running run/job이 있고, 기본적으로 pending은 하나만 유지된다. queue: max를 쓰면 최대 100개 pending을 대기시킬 수 있지만 cancel-in-progress: true와는 함께 쓸 수 없다.
이번에 추가된 것은 네 번째 층이다. 한 job 내부 step 수준의 병렬성이다.
이 층은 다른 병렬성과 역할이 다르다.
- job 병렬성: 서로 다른 runner에서 독립 작업을 나눈다.
- matrix 병렬성: 같은 절차를 여러 입력 조합으로 반복한다.
- concurrency: 공유 배포 대상 같은 외부 리소스의 충돌을 막는다.
- step 병렬성: 같은 runner 안에서 서로 독립인 일을 겹쳐 실행하거나, long-running service를 띄워 놓고 테스트를 돌린다.
따라서 background는 “무조건 빠르게”가 아니라 “같은 runner 안에서 동시에 살아 있어도 안전한가?”를 묻는 도구다.
3. 최신 이슈와 연결
GitHub changelog는 기존 shell backgrounding(&)의 문제로 로그 interleaving을 언급한다. 새 기능은 병렬 실행을 하면서도 step별 로그와 실행 정보를 분리해 보존하는 방향이다.
공식 workflow syntax 문서에는 더 운영적인 제약이 나온다.
background는run또는usesstep에 쓸 수 있다.wait나cancel이 특정 background step을 참조하려면 그 step에id가 있어야 한다.- 한 job에서 동시에 실행되는 background step은 최대 10개이며, 추가 background step은 slot이 날 때까지 대기한다.
- background step의 output, environment 변경, PATH 변경은
wait또는wait-all이후에만 사용할 수 있다. - background step이 실패하면 해당 step을 포함하는 다음
wait또는wait-all에서 job 실패로 전파된다. 단, 해당 background step에continue-on-error를 설정한 경우는 예외다. - 명시적으로 기다리지 않은 background step을 위해 post-job cleanup 전에 암묵적
wait-all이 실행된다. parallel은 self-contained step 그룹을 동시에 실행하고 끝날 때까지 기다리는 편의 문법이다.
runner 구현 PR도 같은 방향을 보여준다. actions/runner #4476은 background step coordinator, wait/wait-all/cancel control-flow, 기본 최대 10개 concurrency semaphore, post-hook 전 implicit wait-all을 추가했다. actions/runner #4479는 background step이 GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH 같은 공유 상태를 직접 쓰지 않고 buffer에 담았다가 wait 시점에 flush하도록 만들었다.
즉 GitHub가 추가한 것은 단순한 YAML sugar가 아니다. shell의 &가 프로세스를 그냥 뒤로 보내는 것이라면, Actions의 background는 runner가 “이 step은 아직 살아 있고, 나중에 기다리거나 취소해야 하며, 실패와 output은 특정 시점에만 반영된다”고 추적하게 만드는 계약이다.
4. 개발자 관점 해석
작은 팀이 이 기능을 바로 쓰고 싶다면 먼저 세 가지를 구분해야 한다.
4.1 parallel이 맞는 경우
독립적인 작업 묶음이 있고 모두 끝나야 다음 단계로 갈 수 있다면 parallel이 더 읽기 좋다.
steps:
- uses: actions/checkout@v4
- parallel:
- name: Build web
run: npm run build:web
- name: Build worker
run: npm run build:worker
- name: Typecheck
run: npm run typecheck
- name: Package artifact
run: npm run package
이 경우 중요한 질문은 “세 작업이 같은 파일을 동시에 쓰지 않는가?”다. 모두 dist/에 덮어쓰거나 같은 cache directory를 수정한다면 병렬화가 flaky build를 만들 수 있다.
4.2 background가 맞는 경우
서비스를 띄워 놓고 뒤 step에서 의존해야 한다면 background가 맞다.
steps:
- uses: actions/checkout@v4
- name: Start test database
id: db
run: docker compose up postgres
background: true
- name: Wait for database readiness
run: ./scripts/wait-for-db.sh
- name: Run integration tests
run: npm run test:integration
- name: Stop test database
cancel: db
여기서 핵심은 cancel이다. CI에서 서비스 프로세스는 “알아서 종료되겠지”가 아니라 “어느 step에서 종료할지”가 명시되어야 한다.
4.3 job concurrency와 함께 봐야 하는 경우
step 병렬화를 해도 배포 대상은 병렬화하면 안 되는 경우가 많다. 예를 들어 staging DB migration, preview environment cleanup, production deploy는 job 내부에서 build와 telemetry upload를 겹쳐 실행할 수는 있어도 같은 environment에 두 run이 동시에 들어가면 안 된다.
concurrency:
group: deploy-${{ github.ref }}
queue: max
또는 개발 branch의 최신 commit만 중요하다면 다음처럼 오래된 run을 취소할 수 있다.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
정리하면 step 병렬성은 “한 runner 안의 시간 절약”이고, concurrency는 “외부 공유 리소스 보호”다. 둘을 혼동하면 빨라진 CI가 더 자주 충돌한다.
5. 내 프로젝트에 적용할 체크포인트
아래 질문에 모두 답할 수 있는 step부터 병렬화하자.
- 이 step은 다른 step과 같은 파일, cache, DB, port, container name을 공유하는가?
- background step의 output이나 env 변경을
wait전에 읽고 있지는 않은가? - 실패가 어느 지점에서 드러나야 하는가? 즉
wait를 어디에 둘 것인가? - long-running service를 정상 종료하는
cancelstep이 있는가? - background 동시 실행 최대 10개 제한에 걸릴 만큼 많은 step을 만들고 있지는 않은가?
- self-hosted runner라면 runner version이 새 syntax를 지원하는 최신 버전인가?
- 배포, migration, release tagging처럼 외부 상태를 바꾸는 job에는 workflow/job
concurrency가 걸려 있는가? - 병렬 실행 후 로그를 읽는 사람이 어느 step 실패인지 30초 안에 찾을 수 있는가?
특히 monorepo에서는 다음 순서가 안전하다.
- 1단계: lint, typecheck, unit test처럼 read-heavy 작업만
parallel로 묶는다. - 2단계: integration test용 DB, mock server 같은 long-running service를
background+ readiness check +cancel로 바꾼다. - 3단계: artifact upload, coverage upload, telemetry upload처럼 결과를 보내는 작업을 뒤 step과 겹친다.
- 4단계: 배포 job은 병렬화보다 concurrency group, environment protection, rollback runbook을 먼저 점검한다.
6. 오늘 10분 액션
오늘은 “가장 긴 CI job 하나”만 보자.
- 최근 10개 workflow run에서 가장 오래 걸린 job을 고른다.
- 그 job의 step을 표로 적는다: 이름, 평균 시간, 입력 파일, 출력 파일, 외부 리소스, 실패 시 영향.
- 서로 독립인 step 두 개만 찾는다. 예:
typecheck와unit test,build:web과build:worker. - 이 둘을
parallel후보로 표시하되, 같은 cache나 output directory를 쓰는지 확인한다. - 서비스 실행 step이 있다면
background후보로 표시하고, readiness check와cancel위치를 적는다. - 배포 job이라면
concurrency.group이 branch, environment, workflow 이름 중 무엇으로 묶여 있는지 확인한다.
10분 안에 코드를 바꾸지 않아도 된다. 오늘의 목표는 “병렬화해도 안전한 step”과 “절대 동시에 실행하면 안 되는 외부 리소스”를 분리하는 것이다.
7. 더 볼 자료
- Actions steps can now be run in parallel
- Workflow syntax for GitHub Actions
- actions/runner #4476 Background steps execution engine
- actions/runner #4479 Add background step deferral infrastructure and metadata plumbing
- Control the concurrency of workflows and jobs
중복 회피 메모
로컬 content/generated와 Supabase의 최근 infra_dev 글을 확인했다. 최근 글은 OpenTelemetry semantic convention 카디널리티, RDS delayed replica, GitHub secret scanning triage, Vercel AI Gateway routing, GitHub Actions pull_request_target 보안, OIDC subject/audience, CI 네트워크 failover, Kubernetes Indexed Job 실패 예산, Cloudflare Queues backlog/DLQ를 다뤘다. 이번 글은 GitHub Actions를 다시 다루지만 권한·OIDC·네트워크가 아니라 2026년 6월 추가된 step-level parallelism의 수명 주기, 실패 전파, output/env deferral, cancel/wait 계약, workflow/job concurrency와의 경계에 집중해 중복을 피했다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)