Skip to content

ci: 테스트 샤딩·잡 병렬화·커버리지 리포트 재사용으로 CI 단축 - #68

Merged
chanwoo7 merged 6 commits into
developfrom
ci/parallel-shards
Oct 4, 2026
Merged

chanwoo7 merged 6 commits into
developfrom
ci/parallel-shards

Conversation

@chanwoo7

@chanwoo7 chanwoo7 commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

배경

  • PR 필수 체크가 끝나기까지 약 3분(144~202초)이 걸렸습니다.
    • check 한 잡이 lint → typecheck → knip → test:cov → build를 직렬로 돌렸고, test:cov가 그중 약 65%였습니다.
    • coverage-report는 같은 테스트를 처음부터 한 번 더 돌렸습니다.
  • 문서는 CI가 codegen 신선도를 검사한다고 적고 있었지만, 실제 CI에는 codegen:check가 없었습니다(로컬 validate만 검사).
  • PR 커버리지 댓글에 base 브랜치와의 비교가 없었습니다(jsonSummaryComparePath: null).

변경

  • CI 워크플로(pr-check.yml)를 잡 단위로 나눠 병렬로 돌립니다. 워크플로 이름 CI와 필수 체크 이름(check·coverage-report·pr-title)은 그대로입니다.
    • lint
    • static: codegen:check(신규) → knip → build && test -f dist/index.html. build가 tsc -b를 돌리므로 별도 typecheck 단계는 뺐습니다.
    • test 3샤드: vitest run --coverage --shard=i/3 + blob 리포터(blob-report/, 보존 1일). 샤드별 임계는 CLI로 0
    • coverage-report: blob을 --merge-reports --coverage로 합쳐 vitest.config.ts 임계를 합친 결과에 적용 → Codecov → PR 댓글
    • check: if: always(), needs 결과가 전부 success인지 검사(건너뜀·취소도 실패)
  • coverage-report는 always()로 돌고, 샤드 결과가 success가 아니면 첫 단계에서 실패합니다. 필수 체크가 건너뜀(skipped)으로 통과 처리되지 않게 하기 위해서입니다.
    • blob 수가 샤드 수와 다르면 실패(부분 커버리지 방지)
  • base 비교를 새로 넣었습니다.
    • develop·main push: coverage-summary 아티팩트(요약·final JSON, 보존 90일). 업로드 실패가 배포를 막지 않게 continue-on-error
    • PR: 이 레포 base 브랜치의 성공한 push 실행에서만 받습니다(base 커밋 실행 우선, 없으면 최신). PR 실행 아티팩트는 쓰지 않습니다. 받은 경우에만 json-summary-compare-path를 넘깁니다.
    • 권한 actions: read는 coverage-report 잡에만
  • 새 액션은 SHA로 고정했습니다: actions/upload-artifact v7.0.1, actions/download-artifact v8.0.1
  • .gitignore에 .vitest/·blob-report/, 가이드 §7·§8과 README의 validate 설명 갱신
  • 커버리지 임계를 실측 정수 내림으로 올렸습니다(합친 수치를 확인한 뒤 별도 커밋).
    • statements 80→95, branches 70→88, functions 80→94, lines 80→96
    • 결정 기록 D16 추가, D7 임계는 대체됨 표시

검증

  • 새 구조에서 CI가 통과했고, 필수 체크 완료까지 약 3분에서 1분 30초~1분 50초로 줄었습니다.

    실행 CI 전체 lint static test 1/2/3 coverage-report check
    이전 PR 10건(조사) 144~202초(중앙 약 182초) – – check 잡 119~197초 85~140초(테스트 재실행) –
    37220367732 (f0cf86b) 104초 33초 33초 45/55/66초 27초 3초
    37220682787 (9c2564f) 93초 40초 35초 51/56/52초 24초 3초
    37221242841 (8f1fa0a) 108초 38초 22초 38/41/71초 24초 2초
    • 대기 경로는 가장 느린 샤드 → coverage-report → check입니다. Analyze(65~78초)는 그보다 먼저 끝났습니다.
    • 샤드는 파일 해시로 나뉘어 실행마다 가장 느린 샤드가 달라집니다(샤드별 테스트 21~45초).
  • 합친 커버리지가 단일 실행과 같았습니다.

    • CI 샤드 합산(3회 모두): 95.54 / 88.07 / 94.29 / 96.07
    • 같은 트리의 로컬 단일 실행(pre-push test:cov): 95.54 / 88.07 / 94.29 / 96.07
  • 부분 커버리지는 임계에서 걸립니다. CI 샤드 blob을 받아 로컬에서 합쳐 봤습니다.

    • 3개: 통과(exit 0)
    • 2개: 76.82 / 68.16 / 69.5 / 77.03 → 4개 지표 모두 미달, exit 1
    • 1개: 50.01 / 38.77 / 40.69 / 50.33 → exit 1
    • 샤드는 임계 0으로 돌고(작은 부분집합으로 로컬 확인, exit 0), 병합만 vitest.config.ts 임계를 적용하는 것도 확인했습니다.
  • 반증: CI에서만 실패하는 테스트를 잠깐 올렸다가(22fbb0e) 되돌렸습니다(8f1fa0a).

    • 실행 37220956009: test (1/3) failure → coverage-report failure("test 샤드 결과: failure") → check failure
    • 셋 다 skipped가 아닌 failure였고, 되돌린 뒤 37221242841에서 다시 전부 success
  • PR 커버리지 댓글은 1개만 달리고, push마다 같은 댓글이 갱신됩니다.

  • base 비교의 대체 경로가 동작했습니다.

    • develop의 base 커밋 push 실행(37215580793)을 찾았지만 예전 워크플로라 아티팩트가 없어 비교 없이 리포트(jsonSummaryComparePath: null)
    • 아티팩트를 실제로 받아 비교하는 경로는 머지 뒤 첫 develop push가 기준을 올린 다음 PR부터 확인할 수 있습니다.
  • actionlint 통과(pr-check.yml), 새 액션은 전부 40자리 SHA 고정

리뷰 반영

  • 적대적 리뷰(별도 에이전트)와 Codex 지적을 반영했습니다.
    • blob 업로드에 overwrite: true를 추가했습니다. 실패한 샤드도 결과를 올리므로 재실행 때 같은 이름이 충돌해 빨간불이 이어졌습니다(main이면 배포가 막힘). 보존 기간도 1일에서 7일로 늘렸습니다.
    • 기준 조회와 PR 댓글을 !cancelled() + 요약 파일이 있을 때로 바꿨습니다. 임계 미달로 병합이 실패해도 댓글이 갱신되어, 이전의 초록 댓글이 남지 않습니다.
    • 기준 아티팩트는 base 커밋 실행을 먼저 보고, 그다음 최신 순으로 최대 5개 실행을 받아질 때까지 시도합니다(Codex P2).
    • 집계 잡 check에 permissions: {}를 두고, pre-push 훅 주석을 새 구조에 맞게 고쳤습니다.

플랜 대조

플랜 항목 2(관리자 FE) 불릿 상태
lint 잡 분리 한 것
static: codegen:check(신규) → knip → build, typecheck 단계 제거 한 것
test 3샤드 + blob, 샤드별 임계 CLI로 0 한 것
coverage-report: blob 병합·임계 검사 → Codecov → push면 기준 아티팩트 → PR이면 기준 비교(없으면 생략) 한 것
check 집계(BE와 같은 방식) 한 것
.gitignore에 .vitest/·blob-report/ 한 것
캐시 추가 안 함(pnpm store 캐시 유지) 한 것
워크플로 이름 CI 유지 한 것
v8 유지 한 것
합친 커버리지가 단일 실행 값과 같은지 확인 한 것 — 95.54/88.07/94.29/96.07 일치
일부러 실패시켜 집계 잡이 빨간색이 되는지 확인 한 것 — 37220956009
임계 상향 80/70/80/80 → 95/88/94/96 한 것

- check 한 잡 직렬(lint → typecheck → knip → test:cov → build) + coverage-report가 테스트를 한 번 더 돌리던 구조를 잡 분리
  - lint / static(codegen:check → knip → build) / test 3샤드 / coverage-report / check 집계
  - typecheck 단계 제거: build가 같은 tsc -b를 돌림
  - codegen:check를 CI에 추가: 문서는 CI가 신선도를 본다고 했지만 실제로는 로컬 validate만 검사했음
- test: vitest --shard=i/3 + blob 리포터(blob-report/, 기본 .vitest/는 숨김이라 업로드에서 빠짐), 샤드별 임계는 CLI로 0
- coverage-report: blob 병합(--merge-reports --coverage)으로 vitest.config.ts 임계를 합친 결과에 적용
  - always()로 돌고 샤드 결과가 success가 아니면 실패: 건너뜀이 필수 체크 통과로 잡히지 않게
  - blob 개수가 SHARD_COUNT와 다르면 실패(부분 커버리지 방지)
  - develop·main push는 coverage-summary 아티팩트(보존 90일, continue-on-error) 업로드
  - PR은 base 브랜치의 성공한 push 실행(같은 레포, base 커밋 우선)에서 받아 json-summary-compare-path로 비교. 없으면 비교 생략
- check: if: always(), needs 결과가 전부 success인지 jq로 검사(skipped·cancelled도 실패)
- .gitignore에 .vitest/·blob-report/, 가이드·README의 CI·validate 설명 갱신
@coderabbitai

coderabbitai Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: CaQuick/caquick-admin-fe/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a10a96ff-1bf3-426b-8d89-1ec6622edea9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 96.07% (🎯 96%) 2447 / 2547
🟢 Statements 95.54% (🎯 95%) 2680 / 2805
🟢 Functions 94.29% (🎯 94%) 1008 / 1069
🟢 Branches 88.07% (🎯 88%) 2017 / 2290
File CoverageNo changed files found.
Generated in workflow #140 for commit 72a6699 by the Vitest Coverage Report Action

@codecov

codecov Bot commented Oct 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0cf86b2c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/pr-check.yml Outdated
Comment on lines +208 to +211
run_id=$(gh api "repos/$REPO/actions/workflows/pr-check.yml/runs?branch=$BASE_REF&event=push&status=success&per_page=50" \
| jq -r --arg repo "$REPO" --arg ref "$BASE_REF" --arg sha "$BASE_SHA" '
[.workflow_runs[] | select(.event == "push" and .head_branch == $ref and .head_repository.full_name == $repo)]
| (map(select(.head_sha == $sha)) + .)[0].id // empty')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry older runs when the selected coverage artifact is missing

When the exact-base or newest successful run has no coverage-summary artifact—for example because the upload is explicitly continue-on-error or the artifact expired—this selects that run before checking artifact availability. The subsequent single download failure merely disables comparison, even if another run among the 50 returned here has a usable artifact, so PR coverage deltas silently disappear; iterate candidate runs until an artifact downloads successfully.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영: 기준 후보를 base 커밋 실행 우선·최신 순으로 최대 5개, 아티팩트가 받아질 때까지 시도.

- statements 80→95, branches 70→88, functions 80→94, lines 80→96
- 근거: PR CI 37220367732의 샤드 합산 95.54/88.07/94.29/96.07 = 같은 트리 단일 실행(로컬 validate)과 일치
- 임계는 리터럴 유지(리포트 액션이 vitest.config.ts에서 정규식으로 읽음)
- 결정 기록 D16 추가, D7 임계는 대체됨 표시
- CI(GITHUB_ACTIONS)에서만 실패하는 테스트 1건: 샤드 실패 시 coverage-report·check가 실패로 끝나는지(건너뜀·성공 아님) 확인용
- 로컬 pre-push는 통과(훅 우회 없이 push)
- 확인 뒤 revert
- 22fbb0e 되돌림. CI 37220956009에서 test (1/3)·coverage-report·check 모두 failure 확인
- blob 업로드 overwrite: 실패한 샤드도 올리므로 재실행 때 같은 이름 충돌로 빨간불이 이어지던 것(main이면 배포 막힘), 보존 1일 → 7일
- 기준 조회·PR 댓글을 !cancelled() + 요약 파일 존재 조건으로: 임계 미달로 병합이 실패해도 댓글을 갱신(이전 초록 댓글이 남지 않게)
- 기준 아티팩트: base 커밋 실행 우선, 최신 순으로 최대 5개 실행을 다운로드될 때까지 시도(Codex P2)
- check(집계) permissions: {}, pre-push 훅 주석을 새 CI 구조에 맞게
@chanwoo7
chanwoo7 merged commit 4bb77df into develop Oct 4, 2026
14 checks passed
@chanwoo7
chanwoo7 deleted the ci/parallel-shards branch October 4, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant