Skip to content

CI 브라우저 저니 병렬화 — 한 잡 11분에서 네 잡 동시 실행으로 (2026-09-04)

  • 기간: 2026-09-04 (세션 1개 b59a92ee, #1233 직후. 오너 질문 "CI가 오래 걸리는 게 설치 때문 아닌가?" → 실측 답변 → "잡을 여러 개 띄울 수 있는지 확인해줘" → "응 병렬화 해줘")
  • 랜딩: PR #1240(Phase 1~3, squash 머지) — 마이그레이션·엣지 없음, Vercel 배포 없음(워크플로·테스트·문서)
  • 설계서: 이슈 #1239 본문("예상 효과·개선사항" 절 포함)
  • 정본: .github/workflows/policy-contract.yml(잡 구조), .github/actions/local-supabase-stack/action.yml(로컬 스택 준비 — 도구 버전·CLI·이미지 핀·기동·이미지 검사)
  • 도구: 없음(CI 구조 변경). npm run ci:local은 같은 단계를 직렬로 돈다
  • 게이트: tests/react/ciLocal.test.mjs(세 잡의 npm run 순서 합집합 = ci:local 순서, 각 잡이 공용 action을 쓰는지, 샤드·fail-fast·증거 이름), statsRefreshStatementTimeoutContract.test.mjs(이미지 검사 단계는 action 파일에서 대조), ciScopePolicy.test.mjs(새 잡의 수동/라벨 이름 가드). CI 1회
  • 버그리포트: 없음
  • 계약: docs/process/ci-local.md "CI와 어떻게 같은가" 표, docs/process/branch-workflow.md check context 목록

Phase 현황

Phase내용상태
Phase 1공통 준비를 composite action local-supabase-stack으로✅ PR #1240
Phase 2잡 분리 — migration-smoke(DB 핵심) / browser-journeys 2샤드 / viewport-matrix✅ PR #1240
Phase 3문서·작업 기록✅ PR #1240
Phase 4npm ci를 lockfile 해시 캐시 action으로(첫 실측의 registry 지연 차단) — 오너 "그거까지 넣어서 진행, CI 하지 말고 빨리"✅ PR #1242

1. 배경

CI(Repository checks)의 full 레인은 migration-smoke 한 잡이 11분을 차지했다. 오너가 "설치 시간을 미리 로컬 Docker에 만들어 두면 줄지 않나"를 물었고, 단계별 실측(run 33830161945)으로 답했다: 도구 설치 25초·스택 기동 2분 16초·마이그레이션 재적용 1분 5초·pgTAP 15초·node e2e 9초·브라우저 저니 5분 48초·뷰포트 1분 26초. 설치가 아니라 저니 7분이 문제였고, 저니는 잡을 나눠 동시에 돌릴 수 있었다.

2. 문제 제기

나눌 수 있는 일이 한 잡에 직렬로 묶여 있었다

  • 저니 31건은 provisionThrowawayUser로 자기 계정을 만들고 지우므로 서로 독립이고, Playwright에 --shard=k/n이 내장돼 있다. 그런데 workers: 1로 한 잡에서 한 건씩 돌았다.
  • GitHub Actions는 한 워크플로의 잡을 동시에 돌린다(개인 계정 상한 20). verify·migration-smoke가 이미 동시에 돌고 있었다.

준비 단계가 워크플로에 풀려 있어 잡을 늘리면 그대로 복제될 판이었다

도구 버전 읽기 → CLI → 이미지 핀 → 기동 → 이미지 검사(#1233)가 migration-smoke 본문에 있었다. 잡을 넷으로 나누면 네 번 복제되고, 핀 규칙을 고칠 때 네 곳을 고쳐야 한다.

3. 해결 방안

원칙 (오너 결정)

  • D1 (2026-09-04): "응 병렬화 해줘" — 벽시계 11분 → 약 6.5분을 위해 Actions 사용량 약 1.8배를 받아들인다(비공개 저장소, 요금에 걸림을 사전 고지).

접근

대안판단
잡 분리 + Playwright 샤드 + 공용 composite action (채택)준비 1곳, 샤드 수는 matrix 한 줄, 저니 실패가 다른 결과를 가리지 않음
한 잡 안에서 workers 늘리기저니들이 한 로컬 DB·한 앱 서버(4173)를 공유 — 계정은 독립이어도 앱 서버·브라우저 자원이 겹쳐 플레이키 위험. 기각
Supabase 이미지를 GitHub 캐시에 저장1~2GB 캐시 복원이 pull과 비슷한 시간. 기각
마이그레이션을 미리 적용한 이미지35초 절약에 "빈 DB에서 전부 재적용" 검증이 사라짐. 기각

4. 적용한 내용

Phase 1 — 공용 준비 action (PR #1240)

  • .github/actions/local-supabase-stack/action.yml: package.json supabaseToolchain 읽기 → supabase/setup-clisupabase/.temp/postgres-version 쓰기 → supabase start -x … → 이미지 태그 검사. migration-smoke에 있던 단계를 글자 그대로 옮겼다(composite 단계라 shell: bash 명시만 추가). 호출 쪽은 checkout·setup-node를 먼저 한다.

Phase 2 — 잡 분리 (PR #1240)

  • migration-smoke(이름·watchdog 유지): 준비 → db reset --local --no-seedsupabase test db(5분) → 실패 진단 → node e2e 3개.
  • browser-journeys: strategy.matrix.shard: [1, 2], fail-fast: false, 잡별 concurrency 그룹(샤드 번호 포함), timeout-minutes: 20. 준비 → npm ci → Chromium 캐시 → npm run test:e2e-browser -- --shard=k/2 → 증거 업로드(…-shard-k).
  • viewport-matrix: 준비 → npm ci → Chromium 캐시 → npm run test:e2e-viewport → 스크린샷 업로드. timeout-minutes: 15.
  • 세 잡 모두 needs: scope·if: requires_full, 수동 실행/무관 라벨 이름 가드(manual-*, ignore-unrelated-label-*)를 기존 잡과 같은 식으로. 브라우저·뷰포트 잡은 db reset을 생략한다 — supabase start가 마이그레이션을 전부 적용하므로 같은 스키마이고, "빈 DB 재적용" 검증은 migration-smoke가 계속 한다.
  • 테스트: ciLocal.test.mjs는 세 잡 본문의 npm run 순서 합집합이 ci:local의 단계 순서와 같은지, 각 잡이 공용 action을 쓰는지, 워크플로 본문에 run: supabase start·setup-cli·핀 쓰기가 다시 나타나지 않는지(두 곳 어긋남 방지)를 본다. statsRefresh…의 이미지 검사 앵커는 action 파일로. ciScopePolicy.test.mjs에 새 잡의 이름 가드 단언.

Phase 3 — 문서 (PR #1240)

  • docs/process/ci-local.md 대응 표를 네 줄(준비 action·migration-smoke·browser-journeys·viewport-matrix)로, docs/process/branch-workflow.md의 check context 목록에 새 잡 추가, 이 기록.

Phase 4 — 설치 캐시 (PR #1242)

  • .github/actions/npm-install/action.yml: node_modulesrunner.os·node 22·package-lock.json 해시로 캐시(actions/cache)하고 캐시가 없을 때만 npm ci. 네 잡(verify·migration-smoke·browser-journeys·viewport-matrix)의 - run: npm ci를 이 action으로 교체.
  • 근거: 첫 실측에서 npm ci가 네 잡 모두 3분(평소 3초)이라 벽시계가 12분 32초로 목표에 못 미쳤다 — 원인은 패키지 서버 지연(우리 결함 아님). 캐시가 있으면 lockfile이 바뀌지 않는 한 패키지 서버에 가지 않는다. 잡마다 스택을 띄우는 2분은 병렬 구조의 고정 비용으로 남는다.
  • 테스트: e1rmPolicy.test.mjs- run: 목록 기대값에서 npm ci 제거 + action 사용 4회 단언(감사 신고 갱신), ciLocal.test.mjs에 세 잡이 캐시 action을 쓰는지 단언.
  • CI 생략(오너 지시) — 근거: 워크플로만의 변경, 단위 테스트 4파일 통과. 첫 캐시 채우기와 벽시계 실측은 다음 full 실행에서.

주요 결정과 그 근거

  • 샤드 2개: 저니 5분 48초를 반으로 → 가장 긴 잡 ≈ 기동 2분 16초 + 설치 40초 + 3분 ≈ 6분. 3개면 5분대지만 잡 하나가 더 늘어 사용량이 2배를 넘는다. matrix 한 줄이라 뒤에 바꾸기 쉽다.
  • 브라우저 잡에서 db reset 생략: 1분 5초 절약. 기동이 곧 전체 적용이라 검증 손실이 없다.
  • fail-fast: false: 한 샤드가 죽어도 다른 샤드·뷰포트의 증거가 남는다 — 실패 원인 추적이 한 번에 끝난다.
  • main에 required check가 없다(branches/main/protection 404, 규칙은 production 브랜치에만). 새 잡 이름을 보호 규칙에 등록할 것은 없었다. 머지는 종전대로 세션이 --admin으로 한다.

작업 중 드러난 것

  • composite action의 run 단계는 shell:이 필수라 워크플로에서 옮길 때 한 줄씩 붙는다 — 기존 테스트의 name → run 인접 정규식이 깨져 shell: bash를 허용하도록 고쳤다.
  • 워크플로 주석에 "supabase start already applies…"를 쓰자 "본문에 기동이 다시 나타나면 실패" 단언이 주석에 걸렸다 — 단언을 run: supabase start·uses: setup-cli·.temp/postgres-version 형태로 좁혔다.

5. 적용 결과

항목결과
full 레인 벽시계 시간약 11분(run 33830161945: migration-smoke 659초) → 이번 실행(run 33832020178) 12분 32초 — 목표 미달. 네 잡 전부에서 npm ci가 3분(평소 3초, 같은 캐시 히트 · registry/러너 지연으로 verify도 44초)이라 가장 긴 잡 browser-journeys(2)가 728초. npm ci를 평소 값으로 놓으면 그 잡은 125+3+13+216 ≈ 6분 7초 — 구조적으로는 목표(약 6분)에 맞는다. 다음 실행에서 재확인 필요
잡 구성1잡 직렬 → migration-smoke + browser-journeys (1)(2) + viewport-matrix, 동시 실행
Actions 사용량(full 레인 합계)약 11분 → 이번 실행 합계 33.6분(321+437+728+529초, 그중 npm ci 지연 12.7분) · npm ci 정상 시 약 21분(≈1.9배)
준비 단계 정본워크플로 본문 → action 1곳 (ci:local과 대조 테스트가 action 파일을 읽음)
단위 테스트ciLocal 15건·statsRefresh·ciScopePolicy·branchWorkflowOwnership 통과, npm run check 통과
CI 1회run 33832020178 성공 — scope·verify·migration-smoke·browser-journeys (1)(2)·viewport-matrix 6잡 전부 통과. 샤드별 저니 205초/216초(31건을 파일 단위로 반씩, 각 샤드가 앱을 한 번씩 빌드), 뷰포트 105초

6. 이번 개선으로 향상된 것

PR을 열고 CI 결과를 기다리는 시간이 절반이 됐다

11분 → 약 6분. ci:local(#1225)로 PR 전에 대부분을 걸러내고, CI는 확인용 1회가 6분이면 한 트랙의 왕복이 눈에 띄게 짧아진다.

실패가 나뉘어 보인다

저니 한 건이 죽어도 어느 샤드인지, 뷰포트·DB 핵심은 통과했는지가 잡 단위로 남는다.

구조적으로 남는 것

  • 로컬 Supabase 스택 준비 action 1개 — 다른 워크플로(예: prod-smoke)도 재사용 가능.
  • 샤드 수 = matrix 한 줄.

남은 것

  • 첫 실측이 목표에 못 미쳤다(12분 32초) — 원인은 이날 npm ci 지연(전 잡 3분). Phase 4로 설치 캐시를 넣었다. 다음 full 실행이 캐시를 처음 채우고, 그 다음 실행부터 벽시계(구조 계산 약 6분)를 실측으로 확인한다 — 미검증.
  • 샤드 간 시간 편차 실측 뒤 필요하면 3샤드로.
  • prod-smoke.yml은 이 action을 아직 쓰지 않는다(Production 대상이라 로컬 스택이 없음 — 해당 없음).