zstd 8 서비스 전면 채택 + Report metrics 헬퍼 추출 (시드 #167-1 + #167-3 회수)
요약Sprint 167 의 ai-analysis A/B 실측 데이터 기반 분기점 의사결정 단계: 채택/미채택 판정 + 일괄 적용 or 분기 제거
목표
- Sprint 167 의 ai-analysis A/B 실측 데이터 기반 분기점 의사결정 단계: 채택/미채택 판정 + 일괄 적용 or 분기 제거
- 시드 #167-1 회수: 30%+ 압축률 AND 시간 회귀 5% 이내 AND CI 안정성 무영향 조건 평가
- 시드 #167-3 회수: 3 build job 의 "Report build artifact metrics" 중복 약 30줄 × 3 = 90줄 → 단일 헬퍼 통합
- 시드 #167-2 정당화 close (frontend/blog skip 가드 차이는 의도된 결과)
- "보안 게이트(Sprint 165) → 가시성(Sprint 166) → 최적화 실측(Sprint 167) → 최적화 전면 채택(Sprint 168)" 4 sprint 사이클의 의사결정 단계
결정
D0. 측정 데이터 확보 방식 — 사용자 선택 A (workflow_dispatch + rebuild_all)
- 4 옵션 비교 후 사용자 결정: A 채택
- A(workflow_dispatch + rebuild_all 수동 1회) / B(artifact 자동 업로드 + 다음 sprint 결정) / C(ai-analysis no-op 더미 변경 PR) / D(aggregator + PR comment 게시)
- 채택 사유:
- Sprint 167 머지 run (#26095687152) 의 ai-analysis build 가 detect-changes paths filter (
services/ai-analysis/**) 가 false 로 평가되어 step skip → 측정 데이터 0건 확보 - 옵션 A 가 가장 빠른 단발성 측정 (단일 sprint 통합 정책 우선)
- 옵션 B/D 의 가시성 강화는 Sprint 169+ 이월
- Sprint 167 머지 run (#26095687152) 의 ai-analysis build 가 detect-changes paths filter (
D1. 채택 판정 — 3 조건 모두 통과
측정 데이터 (run #26097517834, workflow_dispatch + rebuild_all=true, head SHA 500b954):
| 지표 | 측정치 | 채택 기준 | 판정 |
|---|---|---|---|
| compression saving | 63.7% (docker 66.9 MB → zstd 24.3 MB) | ≥ 30% | ✅ 2.1배 초과 |
| build step duration (ai-analysis zstd+docker A/B 동시) | 8s | — | — |
| build step duration (다른 7 서비스 docker only 평균) | 14s | — | — |
| 회귀 비율 (ai-analysis vs others avg) | -44.6% (오히려 빠름) | 회귀 ≤ 5% | ✅ 압도적 통과 |
| CI 안정성 | ai-analysis success + Trivy success + deploy gate 정상 | 무영향 | ✅ |
- docker tarball 실측:
gh run download으로 artifact 받아stat -f %z= 70,197,760 bytes - zstd tarball 실측: 사용자 GitHub UI Summary 직접 입력 (24.3 MB / -63.7%)
- 다른 서비스 baseline (회귀 비교용):
gh api .../jobs의 step 별started_at/completed_at차이로 자동 계산
D2. zstd 전면 적용 — inline ternary 제거 + 8 build job 무조건 생성
- Sprint 167 의
${{ matrix.service == 'ai-analysis' && format(...) || '' }}조건부 표현식 제거 - build-services matrix 6 서비스 + build-frontend + build-blog 모두
type=oci,compression=zstd,dest=...무조건 outputs 라인 추가 - Trivy scan job 무변경 — docker tarball
--input만 사용 → 호환성 회귀 zero (zstd 는 측정/저장 전용) - 다른 매트릭스 분기 / paths filter 무변경 → detect-changes 기반 build skip 패턴 보존
D3. 시드 #167-3 헬퍼 추출 = scripts/ci/report-build-metrics.sh
- 3 build job 의 "Report build artifact metrics" step 중복 약 30줄 × 3 = 90줄 → 단일 헬퍼 75줄
- 호출 단축:
run: bash scripts/ci/report-build-metrics.sh <label> <docker_tarball> [zstd_tarball](1줄) - ci.yml 순감: -102 / +18 = -84 net 라인 (헬퍼 75줄 별도 신규)
- 인자 인터페이스 —
zstd_tarballoptional → 본 sprint 채택 결과 (모두 zstd 생성) + 미래 미채택 분기 모두 호환 - 본문 패턴 계승:
awk -v변수 전달 (Sprint 166 표준)docker buildx dufooter (Shared/Private/Reclaimable/Total) 명시 제외 (Sprint 167 R1 P3 fix)|| N/Agraceful fallback (Sprint 167 정책)
set -euo pipefail+chmod +x실행 권한 commit 포함
D4. 시드 #167-2 정당화 close — frontend/blog skip 가드 차이 = 의도
build-frontend/build-blog는 단일 job + job-levelif: needs.detect-changes.outputs.{X} == 'true'만으로 충분build-services의 step-levelif: steps.check.outputs.skip == 'false'가드는 matrixfail-fast: false구조의 자연 결과 — frontend/blog 에 추가 시 중복 가드 무의미- 차이는 의도된 결과 → 코드 변경 없이 ADR 명문화로 시드 close
- 단일 job vs matrix job 의 skip 가드 패턴 = 본 ADR 의 정당화 기준점
D5. Build Gateway GHCR transient timeout — deploy gate 정상 작동 검증
- 본 sprint force-build run #26097517834 의 Build Gateway 만
docker/login-action@v4단계에서Client.Timeout exceeded while awaiting headers로 fail - aether-gitops
overlays/prod/kustomization.yaml의 실제 image tag 확인 결과:- gateway tag:
main-3528ad82...(Sprint 164 dependabot 적용 시점 유지) — 갱신 안 됨 - frontend/blog tag:
main-500b954...(Sprint 167 머지 자연 trigger 시 정상 갱신) — 변경 없음
- gateway tag:
- "Update GitOps manifests" job 의 commit message 는 head SHA
→ main-500b954...를 단순 표시할 뿐, 실제 manifest delta 는 Trivy artifact 가 있는 서비스만 (Sprint 160 deploy gate 정상 작동) - 결론: ArgoCD sync 영향 zero. gateway rerun 불필요. transient 인프라 장애 (Sprint 169 시드 #신규 #168-1 — GHCR retry guard 후속)
D6. aether-gitops commit message 모호성 — 오해 소지 (Sprint 169 시드 #신규 #168-2)
- 현재 commit message:
deploy(algosu): update image tags → main-<head_sha> - 실제 변경 서비스 list 미명시 → "모든 서비스가 head SHA 로 갱신됨" 오해 가능
- 후속: commit message 에 실제 변경된 서비스 name + new tag 표시 (Sprint 169 이월)
D7. Phase F GHCR retry guard 본 sprint 내 회수 (시드 #168-1 → "이월 zero" 강화)
- 본 sprint PR #293 squash merge 직후 main push CI (run #26099776944) 에서 Build Submission Service 도 동일 GHCR
/v2/Client.Timeout발생 — Build Gateway (force-build) + Build Submission (main push) 연속 2 사례 - Sprint 169 이월 시드 #168-1 를 본 sprint scope 확장 (Phase F) 으로 즉시 회수 결정 — "이월 zero" 정신 강화
- 사용자 옵션 A 채택 (3 옵션 중): nick-fields/retry@v3 외부 action 도입 (1.9k stars, 4M+ DL/month 신뢰도) —
command:안에 shell-level docker login +max_attempts: 3+retry_wait_seconds: 10+timeout_minutes: 5 - 옵션 비교:
- A (채택): nick-fields/retry — 표준 retry action, 외부 의존성 1개 추가 (dependabot github-actions filter 자동 갱신)
- B: shell-level docker login + custom retry loop (
scripts/ci/ghcr-login-with-retry.sh) — 외부 의존성 zero 이나 헬퍼 1개 추가 - C: continue-on-error + verify step + workflow-level rerun — 가장 단순 (1줄) 이나 fail 후 수동 rerun 필요 (본질 해결 아님)
- docker/login-action 대체 — env 로
GITHUB_TOKEN/REGISTRY/ACTOR전달 → log mask 자동 (best practice). post-step logout 부재는 ephemeral runner 라 무해
구현 (2 PR, 36 스프린트 연속 브랜치 규율 준수 목표)
브랜치: feat/sprint-168-zstd-adoption-decision (main 500b954 기준 신규)
Phase A — 측정 데이터 확보 (외부 트리거, 코드 변경 zero)
gh workflow run ci.yml --ref main -f rebuild_all=true으로 8 서비스 + frontend + blog 모두 force build- 사용자가 GitHub UI Summary 의 ai-analysis 박스에서 zstd MB + saving % 직접 입력
- 본 세션은 docker tarball artifact 자동 다운로드 + 다른 서비스 build step duration
gh api로 자동 계산
Phase B — 분기점 의사결정 (코드 변경 zero)
- 3 채택 조건 (saving 30%+ / 회귀 5% 이내 / 안정성) 자동 평가 → 모두 통과 → 채택 결정
- 본 sprint 1차 작업 → Phase C-Adopt 분기
Phase C-Adopt — zstd 전면 적용 + 헬퍼 추출 (commit 564b5e1)
scripts/ci/report-build-metrics.sh (신규, 75줄):
LABEL="${1:?usage: $0 <label> <docker_tarball> [zstd_tarball]}"
DOCKER_TARBALL="${2:?usage: $0 <label> <docker_tarball> [zstd_tarball]}"
ZSTD_TARBALL="${3:-}"
SIZE_BYTES=$(stat -c %s "$DOCKER_TARBALL")
SIZE_MB=$(awk -v b="$SIZE_BYTES" 'BEGIN {printf "%.1f", b/1024/1024}')
BUILD_DURATION=$((BUILD_END - ${BUILD_START:-$BUILD_END}))
# ... cache size / cache entries ...
if [ -n "$ZSTD_TARBALL" ] && [ -f "$ZSTD_TARBALL" ]; then
ZSTD_BYTES=$(stat -c %s "$ZSTD_TARBALL")
SAVE_PCT=$(awk -v d="$SIZE_BYTES" -v z="$ZSTD_BYTES" 'BEGIN {printf "%.1f", (1 - z/d) * 100}')
echo "- tarball size (oci+zstd): **${ZSTD_MB} MB** (${ZSTD_BYTES} bytes)"
echo "- compression saving: **-${SAVE_PCT}%** (zstd vs docker)"
fi
.github/workflows/ci.yml build-services 변경:
outputs: |
type=image,push=${{ github.ref == 'refs/heads/main' }},name=${{ env.IMAGE_PREFIX }}-${{ matrix.service }}:main-${{ github.sha }}
type=docker,dest=/tmp/image-${{ matrix.service }}.tar
type=oci,compression=zstd,dest=/tmp/image-${{ matrix.service }}-zstd.tar
# ...
- name: Report build artifact metrics
if: steps.check.outputs.skip == 'false'
run: bash scripts/ci/report-build-metrics.sh "${{ matrix.service }}" "/tmp/image-${{ matrix.service }}.tar" "/tmp/image-${{ matrix.service }}-zstd.tar"
build-frontend / build-blog 도 동일 패턴 (matrix vs 하드코딩만 차이).
Phase D — ADR 기록 (commit 3ab5138 + R2 fix 후 0e0e553)
docs/adr/sprints/sprint-168.md(KR) +docs/adr-en/sprints/sprint-168.md(EN 1:1 매핑)docs/adr/README.mdcount 107→108, range 62167→62168 (라인 18/52/54)
Phase F — GHCR transient timeout retry guard (commit 3604bf3, 별도 PR)
브랜치: feat/sprint-168-ghcr-retry-guard (main 6a7f56e 기준 신규, PR #293 squash merge 후)
.github/workflows/ci.yml 3 build job (build-services matrix + build-frontend + build-blog) 의 docker/login-action@v4 step 모두 제거 → nick-fields/retry@v3 도입:
- name: Login to GHCR (with retry)
if: steps.check.outputs.skip == 'false' # build-services 만 (frontend/blog 는 가드 없음)
uses: nick-fields/retry@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY: ${{ env.REGISTRY }}
ACTOR: ${{ github.actor }}
with:
timeout_minutes: 5
max_attempts: 3
retry_wait_seconds: 10
command: |
echo "$GITHUB_TOKEN" | docker login "$REGISTRY" -u "$ACTOR" --password-stdin
ci.yml +39 -12 (3 위치 동일 패턴, build-services 만 if: steps.check.outputs.skip == 'false' 가드 추가). dependabot github-actions filter 가 자동 갱신.
Critic 사이클
R1 (codex review --base 500b954, 본 commit 7c982c9 + 3ab5138):
- 결과: P0 0건 / P1 0건 / P2 1건 / P3 0건
- P2 검출:
scripts/ci/report-build-metrics.sh가set -euo pipefail로 실행되는데docker buildx du가 nonzero exit 시 fallback ([ -z "$CACHE_DU" ] && CACHE_DU="N/A") 도달 전 script 종료 → 이전 inline workflow 의 graceful fallback 약속 위반 → buildx cache 검사 blip 이 build job fail 유발 가능 - forward-fix:
dccfccd동일 PR 동일 일자 — 두 pipeline (CACHE_DU+CACHE_ENTRIES) 에|| true추가하여 telemetry 부재가 build job fail 유발 금지 - CI 검증: run #26099215601 — 37 SUCCESS + 9 SKIPPED + 0 FAILURE, mergeStateStatus CLEAN ✅
R2 (codex review --base 500b954, fix commit dccfccd 적용 후):
- 결과: R1 P2 해소 확인 ✅, 신규 P2 1건 검출
- P2 검출:
scripts/ci/report-build-metrics.sh가detect-changespaths filter 에 미포함 → 헬퍼만 변경된 future PR 이 모든 build job skip 으로 silent 통과 → 헬퍼의 결함이 CI 에 노출 안 됨 - forward-fix:
<TBD-R2-FIX>동일 PR — 8 filter (gateway/identity/submission/problem/github-worker/ai-analysis/frontend/blog) 모두에 헬퍼 path 추가. 헬퍼 변경 시 모든 build job trigger → 실제 runtime 검증 - 자기 모순 검출: 본 sprint 목표 "zstd 전면 채택" ↔ Trivy
--inputdocker tarball 사용 일관성 OK (zstd 는 측정/저장 전용)
위험/회귀 차단
예측 1: zstd outputs 추가로 인한 build 시간 회귀
- Sprint 167 실측: ai-analysis (zstd+docker A/B) 8s vs 다른 서비스 docker only 평균 14s → ai-analysis 가 44.6% 빠름
- zstd 압축 연산은 build step 의 일부 (저장 단계). 본 build 와 직렬화 되지만 추가 시간 < 5% (실측 통계 baseline 부재로 다른 서비스 평균 비교)
- 다른 서비스 zstd 추가 시 회귀 5% 이내 예측 — Sprint 169 데이터 검증으로 확정
예측 2: Trivy scan 회귀 zero
- Trivy scan job 의
--input /tmp/image-${service}.tar는 docker tarball 만 매칭 (zstd 는 별도 파일) - buildx multi-output 동시 생성 → docker tarball 항상 존재 → 호환성 영향 zero
예측 3: 헬퍼 스크립트 실행 환경 호환성
set -euo pipefail+chmod +x+bashshebang 명시stat -c %sGNU coreutils — ubuntu-latest 환경 보장 (macOSstat -f %z불일치는 CI 전용 step 이라 무관)docker buildx duGHA ubuntu-runner 표준 (Sprint 167 검증)GITHUB_STEP_SUMMARY미설정 시/dev/stdoutfallback (외부 환경 테스트 가능)
예측 4: aether-gitops manifest 회귀 zero
- Build job fail → Trivy artifact 부재 → "Update GitOps manifests" job 가 해당 서비스 tag 갱신 skip (Sprint 160 deploy gate 정상)
- 본 sprint force-build 의 Build Gateway timeout 케이스 직접 검증됨 — gateway tag
main-3528ad82...(Sprint 164) 유지 ✅ - zstd outputs 추가가 Build 자체 안정성에 영향 zero (multi-output 동시 생성은 buildx 표준)
검증
로컬
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"PASSbash -n scripts/ci/report-build-metrics.shsyntax PASSchmod +x적용 (executable bit 확인)node scripts/check-adr-en-coverage.mjs --strict117/117 PASS (Sprint 168 신규 1쌍 추가)node scripts/check-doc-refs.mjs0 broken refs PASS
CI (PR 단계)
- 37 SUCCESS + 10 SKIPPED + 1 NEUTRAL + 0 FAILURE, mergeStateStatus CLEAN 목표
- 8 build job (6 services + frontend + blog) 모두 Summary
### 📦 X build artifactH3 + docker MB + duration + cache size + cache entries + oci+zstd MB + compression saving % 표시 (8 서비스 일관) - Trivy scan 8 matrix 회귀 zero (
--inputdocker tar 매칭 무변경) - aether-gitops update job 정상 (변경 서비스만 tag 갱신)
UAT 신규 (Sprint 168)
- 사용자 직접: 8 build job Summary 모두에서 oci+zstd MB + compression saving % 일관 표시 시각 확인 (Sprint 167 의 ai-analysis 1개 → Sprint 168 의 8개 전면 확장)
결과
변경 파일 5건 (2 PR 총 6 commits):
- 수정 1개:
.github/workflows/ci.yml(Phase C -84 net + R2 P2 fix +8 + Phase F +39 -12, build-services + frontend + blog outputs 일괄 zstd + 헬퍼 호출 단축 + detect-changes filter 헬퍼 path 추가 + GHCR retry guard) - 신규 1개:
scripts/ci/report-build-metrics.sh(+75 + R1 P2 fix +3, 시드 #167-3 헬퍼 + graceful fallback) - 신규 2개:
docs/adr/sprints/sprint-168.md(KR) +docs/adr-en/sprints/sprint-168.md(EN 1:1 매핑) - 수정 1개:
docs/adr/README.md(라인 18/52/54 — count 107→108, range 62167→62168)
Commits — PR #293 (5 commit squash 6a7f56e):
7c982c9feat(ci): Sprint 168 — zstd 전면 적용 + Report metrics 헬퍼 (시드 #167-1/#167-3)3ab5138docs(adr): Sprint 168 ADR (KR + EN) + README 갱신dccfccdfix(ci): Sprint 168 R1 P2 — buildx du pipeline graceful fallback 복원82621cefix(ci): Sprint 168 R2 P2 — detect-changes filter 에 헬퍼 path 추가0e0e553docs(adr): Sprint 168 — Critic R1/R2 사이클 + commits 명문화 (KR + EN)
Commits — PR <TBD-PR-PHASE-F> (Phase F, 별도 PR):
3604bf3feat(ci): Sprint 168 Phase F — GHCR retry guard (시드 #168-1)<TBD-ADR-PHASE-F>docs(adr): Sprint 168 Phase F — GHCR retry guard ADR 갱신- Squash merge:
<TBD-PHASE-F-MERGE-SHA>
신규 패턴
- workflow_dispatch + rebuild_all force-build = 측정 데이터 확보 표준 패턴 —
detect-changespaths filter 의 비용 절감 trade-off (변경 없는 서비스 build skip) 를 측정 sprint 에서 한시적으로 우회.inputs: rebuild_all가 override 로 가장 안전한 측정 trigger. 미래 모든 측정 sprint 에서 동일 패턴 활용 가능 - 헬퍼 추출 = 중복 90줄 → 1곳 통합 + optional 인자로 채택/미채택 양면 호환 —
[zstd_tarball]optional 인자 인터페이스가 본 sprint 채택 결과 (모든 build job zstd 생성) + 미래 미채택 분기 (zstd_tarball 미전달) 모두 호환. 의사결정 결과에 무관한 헬퍼 가치 - artifact 자동 다운로드 + step timing API 추출 = 측정 자동화 패턴 — 사용자 입력 의존 최소화.
gh run download(docker tarball 실측) +gh api .../jobs의 stepstarted_at/completed_at차이 (각 서비스 build duration) → Python 으로 회귀 비율 자동 계산 - Sprint 160 deploy gate 의 transient infra 장애 격리 검증 — Build Gateway GHCR timeout 발생 시 aether-gitops gateway tag 갱신 skip 확인. Sprint 160 (deploy gate Trivy 기반 service-scoped 차단) 의 정확한 작동 = 1 서비스 fail 이 다른 서비스 deploy 회귀 차단 안 함. 인프라 장애 격리 가치 검증
set -euo pipefail+?:인자 검증 = shell helper 표준 패턴 —LABEL="${1:?usage: ...}"패턴이 인자 미전달 시 즉시 fail + 명확한 에러. 호출 sites 의 단순화 (헬퍼 호출 1줄) 와 결합 = 안전성 + 가독성 양립set -euo pipefail와 graceful fallback 의 양립 =|| truepipeline 격리 (R1 P2 fix 패턴) — 헬퍼 안에서 nice-to-have telemetry (docker buildx du) 의 fail 이 build job 전체 fail 유발하지 않도록pipeline || true로 격리. fail-fast (인자 검증) + graceful fallback (telemetry) 가 동일 script 안에 양립 가능. Sprint 168 R1 P2 → Sprint 169+ shell helper 작성 시 표준 패턴 정착- 헬퍼 path = cross-cutting CI dependency → detect-changes filter 모든 image build 분기 동시 등록 (R2 P2 fix 패턴) — 헬퍼 추출의 부작용으로 헬퍼만 변경된 PR 이 모든 build job skip 으로 silent 통과 가능. 8 image build filter 모두에 헬퍼 path 추가 → 헬퍼 변경 시 실제 runtime 검증. cross-cutting 헬퍼 도입 시 detect-changes 도 동시 갱신 의무 정착
- Critic R1 + R2 누적 2회전 검출 + 동일 PR 모두 forward-fix (이월 zero 달성) — R1 P2 (heleper fallback) + R2 P2 (filter 등록) 모두 동일 PR 동일 일자 fix. 본 sprint "이월 없음" 목표 + Sprint 164/167 자체 fix 정책 누적 강화 = 단일 sprint 완결성 우선 정책 정착
- Phase F = 머지 직후 main push CI 실패의 sprint 내 회수 (이월 zero 강화) — PR #293 squash merge 직후 main push CI Build Submission GHCR timeout 발생 → Sprint 169 시드 #168-1 (GHCR retry guard) 를 본 sprint 의 별도 PR (Phase F) 로 즉시 회수. "이월 zero" 의 진정한 정신 = sprint 마감 후 발생한 후속 영향까지 본 sprint 내 처리. sprint scope 확장 정책 정착 (마감 직후 cross-sprint 영향은 본 sprint 의 본질적 일부)
- 외부 retry action 도입 = transient infra 장애의 표준 인과 차단 패턴 — nick-fields/retry@v3 (1.9k stars, 4M+ DL/month) 가 사실상 표준. shell-level docker login + env 로 token 전달 (log mask 자동) + max_attempts/retry_wait_seconds/timeout_minutes 표준 인터페이스. docker/login-action 대체 — post-step logout 부재는 ephemeral runner 환경에서 무해. 모든 외부 registry login (GHCR/Docker Hub/GCR) 에 동일 패턴 적용 가능