개념
릴리스 회의에서 답하지 못하는 질문
배포 직전 회의에서 반복되는 질문이 하나 있다. "그래서 이 버전에서 무엇이 검증됐나요." 이 질문에 즉시 답하는 조직은 드물다. 보통 이런 대화가 된다 — 어제 회귀 테스트가 돌았고, 통과율이 높았고, 그런데 그게 지금 배포하려는 빌드였는지는 확실하지 않고, 환경은 스테이징이었던 것 같다.
자동화가 아무리 많아도 결과가 버전에 붙어 있지 않으면 판정에 쓸 수 없다. 테스트 결과와 배포 대상 사이의 연결이 사람의 기억이면, 그 연결은 회의실에서 끊긴다. 이 레슨은 그 연결을 데이터로 만드는 일을 다룬다.
이 레슨의 경계 — 두 종류의 상관 ID
"상관 ID"라는 말이 이 커리큘럼에 두 번 나오고, 가리키는 것이 다르다.
| m55 레슨 3의 상관 ID | 이 레슨의 상관 식별자 | |
|---|---|---|
| 잇는 대상 | 테스트 실행 ↔ 서버 로그·트레이스 | 커밋 ↔ 빌드 ↔ 환경 ↔ 테스트 결과 ↔ 릴리스 |
| 목적 | 실패 1건의 원인을 5분 안에 좁힌다 | 버전 단위로 무엇이 검증됐는지 조회한다 |
| 수명 | 실패 조사 기간(며칠) | 릴리스 이력(수년, 감사 대응 포함) |
| 쓰는 사람 | 실패를 조사하는 엔지니어 | 릴리스 판정자, 감사·규제 대응, 사고 조사 |
둘은 같이 쓰면 가장 좋다. 실행 레코드에 두 ID 를 모두 남기면, 릴리스 증거에서 출발해 특정 실패의 서버 로그까지 한 줄로 내려갈 수 있다. 다만 설계 목적이 달라서 혼동하면 어느 쪽도 제대로 안 된다 — 깊이를 위한 ID 와 추적을 위한 ID 는 따로 정의한다.
묶는 다섯 축
추적 가능한 상태란 다섯 축이 한 레코드에서 서로를 가리키는 상태다.
① 소스 커밋 식별자, 브랜치, 태그
② 빌드 빌드 번호 + 산출 아티팩트의 다이제스트(내용 해시)
③ 환경 환경 좌표 해시 (레슨 3) — 구성·스키마·시드·의존 서비스
④ 실행 run_id — 테스트 실행 1회를 가리키는 식별자
⑤ 결과 테스트별 상태·소요·분류 라벨 (레슨 5의 실행 레코드)핵심은 ②의 다이제스트 다. 빌드 번호는 재빌드하면 바뀌고, 커밋만으로는 "같은 커밋을 다시 빌드한 다른 산출물"을 구분할 수 없다. 아티팩트의 내용 해시를 기록하면 스테이징에서 검증한 것과 운영에 올리는 것이 같은 물건인지 를 기계적으로 확인할 수 있다. 이것이 없으면 "테스트는 통과했지만 배포된 건 다른 빌드"라는 사고가 가능해진다.
여기서 나오는 운영 원칙이 불변 아티팩트와 승격(promotion) 이다.
한 번 빌드, 여러 환경 승격
빌드 1회 → 아티팩트 A (digest sha256:7f3a…)
↓ 배포 dev 환경좌표 h1 → 테스트 결과 R1
↓ 같은 A 배포 staging 환경좌표 h2 → 테스트 결과 R2
↓ 같은 A 배포 prod 환경좌표 h3 → 운영 모니터링
금지: 환경마다 다시 빌드한다 (아티팩트가 달라져 검증 결과가 승계되지 않는다)환경마다 재빌드하는 파이프라인에서는 스테이징의 테스트 결과를 운영 배포의 근거로 쓸 수 없다. 논리적으로 다른 산출물이기 때문이다. 이 원칙은 m14 레슨 1·4에서 다룬 파이프라인 설계의 전제이기도 하지만, 다이제스트를 결과에 기록해 그 전제가 지켜졌음을 증명하는 일 은 TestOps 쪽 책임이다.
식별자 설계 — run_id 를 어떻게 만드는가
run_id 는 짧고, 사람이 읽을 수 있고, 충돌하지 않고, 다른 축으로 역추적 가능해야 한다. 실무에서 쓰기 좋은 형태는 구성 요소를 합성하는 방식이다.
run_id = {시각}-{커밋 앞 7자}-{환경}-{실행 종류}-{짧은 난수}
예) 20261003T0210-7f3a1c9-staging-nightly-k3x이 형식의 장점은 눈으로 보고도 맥락을 알 수 있다는 것이다. 알림 메시지나 티켓 제목에 그대로 넣을 수 있고, 로그 검색 키로도 쓸 수 있다. 데이터베이스에는 이 문자열과 함께 각 축을 별도 열로도 넣는다 — 문자열 파싱에 의존하면 형식을 바꾸는 날 모든 쿼리가 깨진다.
그리고 run_id 는 테스트 데이터에도 심는다. 레슨 3에서 데이터 소유자 식별에 쓴 그 식별자가 바로 이것이다. 같은 ID 가 실행 레코드·생성 데이터·아티팩트 경로·알림 메시지에 모두 들어가면, 어느 지점에서 출발해도 나머지를 찾을 수 있다.
품질 증거 요약 — 릴리스 판정에 붙이는 한 장
품질 게이트(m14 레슨 4)는 기계가 자동으로 차단하는 기준이다. 반면 릴리스 판정은 사람이 하는 결정이고, 그 결정에는 게이트 통과 여부보다 더 많은 맥락이 필요하다. 그 맥락을 정해진 형식으로 제공하는 것이 품질 증거 요약이다.
{
"release": "2026.10.03-r1",
"artifact_digest": "sha256:7f3a1c9e4b2d...",
"commit": "7f3a1c9",
"verified_on": [
{ "env": "staging", "env_coordinate": "h2c81f", "run_id": "20261003T0210-7f3a1c9-staging-nightly-k3x" }
],
"summary": {
"total": 512,
"passed": 486,
"failed": 4,
"flaky_pass": 7,
"quarantined": 15,
"skipped": 0
},
"open_failures": [
{ "test_id": "chk-order-refund-02", "triage": "product-defect", "ticket": "QA-4821", "severity": "major" },
{ "test_id": "chk-search-rank-11", "triage": "test-defect", "ticket": "QA-4830", "severity": "minor" }
],
"coverage_gap": [
"환불 부분취소 — 격리 중(QA-4702), 수동 확인으로 대체함",
"결제 3DS 실패 경로 — 자동화 없음, 수동 체크리스트 항목 17"
],
"notes": "결제 모의 서버 1.8.0 기준. 실기기 iOS 검증은 야간 전체 실행 범위 밖."
}이 형식에서 중요한 네 부분은 숫자가 아니다.
verified_on— 어떤 환경 좌표에서 검증했는지. 운영과 다른 좌표였다면 그 차이가 판정의 전제가 된다.quarantined— 격리된 15개는 검증되지 않았다는 뜻이다. 통과율만 보면 숨는다.coverage_gap— 격리·미자동화로 생긴 공백과 대체 수단. 레슨 4의 장부에서 온다.open_failures의triage— 남은 실패가 제품 결함인지 테스트 결함인지. 레슨 7의 분류 결과가 그대로 들어온다.
통과율 하나로 릴리스를 판정하지 않게 만드는 것 이 이 형식의 목적이다. "95% 통과"는 판정에 쓸 수 없고, "제품 결함 1건 major 로 남았고 환불 부분취소는 격리 중이며 수동으로 대체 확인함"은 판정에 쓸 수 있다.
추적 가능성 사슬 — 양방향으로 물어본다
추적 가능성이 작동하는지 확인하는 방법은 사슬을 양쪽에서 걸어 보는 것이다.
앞에서 뒤로 (변경 → 검증)
요구사항 / 티켓
→ 그 티켓을 구현한 커밋
→ 그 커밋을 담은 빌드 아티팩트(digest)
→ 그 아티팩트를 검증한 실행(run_id)과 환경 좌표
→ 그 실행에서 관련 테스트의 결과
→ 그 결과를 담은 릴리스 증거
뒤에서 앞으로 (사고 → 원인)
운영 장애 발생
→ 지금 돌고 있는 아티팩트 digest
→ 그 digest 를 검증한 릴리스 증거
→ 그 증거의 coverage_gap — 이 장애 경로가 공백에 있었는가
→ 있었다면 왜 공백이었는지, 없었다면 왜 못 잡았는지두 번째 방향이 사고 회고에서 가장 쓸모 있다. "왜 테스트가 못 잡았나"라는 질문에 감정 없이 답할 수 있게 된다 — 공백 목록에 적혀 있었다면 알고 감수한 위험이고, 적혀 있지 않았다면 자동화의 맹점을 새로 발견한 것이다. 이 구분이 없으면 회고는 "테스트를 더 잘 했어야 한다"로 끝나고 아무것도 바뀌지 않는다.
규제 산업이나 외부 감사가 있는 조직에서는 이 사슬 자체가 요구사항이 된다. 적용되는 표준과 그 요구 범위는 산업별로 다르므로 품질보증·규제 담당과 원문 확인이 필요하다. 다만 기술적 준비는 같다 — 다섯 축이 한 레코드에서 서로를 가리키면 대부분의 추적 요구는 충족된다.
아티팩트 보관 정책 — 재현을 위한 최소치
보관 정책이 없는 조직은 두 극단 중 하나에 있다. 전부 영구 보관해 저장소 비용이 커지거나, 짧은 기본값을 그대로 둬서 조사할 때 아무것도 없다. 층으로 나누는 것이 답이다.
| 층 | 무엇 | 권장 보관 | 근거 |
|---|---|---|---|
| 실행 레코드 | 테스트별 상태·소요·분류 (구조화 한 줄) | 1~3년 | 용량이 작고 추세·감사에 쓰인다 |
| 결과 리포트 | 실행 1회의 요약 리포트 | 6~12개월 | 과거 실행을 사람이 다시 읽는 상한 |
| 실패 아티팩트 | 실패 건의 스크린샷·트레이스·로그 | 30~90일 | 조사 수요가 급격히 떨어지는 구간 |
| 통과 아티팩트 | 통과 건의 비디오·트레이스 | 수집하지 않거나 수일 | 거의 열리지 않는다 (레슨 2 감축 1번) |
| 릴리스 증거 | 품질 증거 요약 JSON | 릴리스와 동일 수명 | 사고 조사·감사의 출발점 |
| 빌드 아티팩트 | 배포 산출물 자체 | 릴리스 수명 + 롤백 기간 | 같은 digest 로 재배포·재현 |
정책에 반드시 붙여야 하는 예외 규칙이 하나 있다. 보관 기간을 연장하는 트리거 다. 특정 실행이 사고 조사나 릴리스 판정의 근거가 되면, 그 실행에 보존 표시를 붙여 정리 작업에서 제외한다. 표시 없이는 30일 뒤 조사 근거가 사라지고, 이 사고는 되돌릴 수 없다.
보존 표시(hold)를 붙이는 경우
- 릴리스 증거로 참조된 실행
- 사고·장애 회고에서 인용된 실행
- 열린 결함 티켓이 가리키는 실패 건
- 감사·외부 보고에 제출된 실행
→ 티켓이 닫히거나 릴리스 수명이 끝날 때 해제실무에서 왜 필요한가
연계가 없을 때 드는 비용은 사고가 날 때 한꺼번에 나타난다. 운영 장애가 터지고, 지금 돌고 있는 버전이 무엇인지 확인하는 데 30분, 그 버전이 어떤 테스트를 통과했는지 확인하는 데 몇 시간이 든다. 그 사이 롤백 판단이 지연되고, 지연된 만큼 사용자가 겪는다. 다섯 축이 묶여 있으면 이 조회는 몇 분이다.
평상시의 효용은 다른 데 있다. 릴리스 판정 회의의 성격이 바뀐다. 증거 요약이 자동 생성되면 회의는 "테스트 돌았나요"를 확인하는 자리가 아니라 남은 위험을 받아들일지 결정하는 자리 가 된다. QA 의 발언도 달라진다 — "조금 불안합니다"가 아니라 "제품 결함 1건이 major 로 남아 있고, 환불 부분취소 경로는 격리 중이라 수동 확인으로 대체했습니다"가 된다. 후자는 반박할 수도, 받아들일 수도 있는 정보다.
보관 정책은 조용한 비용을 양쪽에서 막는다. 과도한 보관은 저장소 비용으로, 짧은 보관은 조사 불가로 나타난다. 층을 나누고 보존 표시를 두는 설계는 둘 다 피하면서, 무엇보다 "그때 자료가 없어서 원인을 못 찾았다"는 결말을 막는다.