14. 품질과 신뢰성 — 위험을 증거와 예산으로 관리한다
완벽한 검증은 불가능하고 100% 신뢰성은 목표가 될 수 없다. 그래서 품질과 신뢰성은 "얼마나 좋은가"의 절대값이 아니라 판단 문제다. 이 챕터는 테스트·리뷰·지표를 위험을 줄이는 증거(evidence)의 구매로, 신뢰성 목표를 실패를 허용하는 예산(budget)의 운영으로 재구성하고, 두 활동을 배포 중단·부채 상환 같은 실제 행동에 연결하는 판단 모델을 세운다.
학습 목표
- 100% 신뢰성이 목표가 될 수 없는 이유를 비용 곡선과 사용자 체감으로 설명한다.
- 검증 수단(테스트 수준·리뷰·정적 지표·SLI)별로 사는 증거와 사지 못하는 것의 경계를 구분한다.
- 위험과 비용을 근거로 검증 수단의 배치 순서를 판단한다.
- 에러 버짓 같은 예산 소진 신호를 배포 중단·상환 우선순위 같은 행동에 연결한다.
배경: 무엇을 알았더라면 그 배포를 막을 수 있었나
챕터 0의 주문 확정 API 장애를 다시 보자. 13:55에 재고 서비스 SDK가 업데이트되어 기본 재시도 횟수가 0회에서 2회로 바뀌었고, 14:05부터 p99가 400ms에서 9초로 치솟았으며 중복 주문이 0.3% 발생했다. 챕터 0에서는 이 장애를 어떻게 진단하는지 다뤘다. 이번에는 시간을 배포 전으로 되돌려 묻는다. 어떤 증거가 있었다면 이 배포를 막거나 피해를 줄였을까?
- 단위 테스트가 잡았을까? 애플리케이션 코드는 한 줄도 바뀌지 않았다. 의존성 버전 하나가 올라갔을 뿐이고, 문제는 SDK의 기본값과 부분 실패 상황의 상호작용에서 나왔다. 애플리케이션 로직만 검증하는 단위 테스트는 이 위험을 관측할 수 없는 위치에 있다.
- 통합 테스트가 잡았을까? 재고 서비스가 느려지는 조건을 주입하는 테스트가 있었다면 "타임아웃 시 총 시도 수가 1회여야 한다"는 가정의 위반을 잡을 수 있었다. 그러나 그런 테스트는 그 가정을 지켜야 할 불변식으로 먼저 명시했을 때만 존재한다.
- 코드 리뷰가 잡았을까? diff는 lockfile의 버전 숫자 변경이다. 리뷰어가 SDK의 변경 이력에서 기본값 변화를 찾아내길 기대하는 것은 리뷰가 실제로 잘 잡는 결함의 범위를 벗어난다.
- 배포 후 관측이 잡았을까? 트래픽의 1%에만 새 버전을 노출하는 카나리 배포와 p99·중복 주문율을 보는 SLI가 있었다면, 전체 장애 대신 수 분 안에 자동 롤백으로 끝났을 수 있다.
정답이 하나가 아니라는 점이 이 챕터의 출발점이다. 어떤 증거도 공짜가 아니고, 어떤 증거도 모든 위험을 덮지 못한다. 통합 테스트는 작성·유지 비용이 비싸고, 카나리는 일부 사용자를 실제 위험에 노출한다. 그래서 필요한 것은 "테스트를 더 열심히"가 아니라 어떤 위험에 어떤 증거를 얼마의 비용으로 살지, 남는 위험을 어떤 예산으로 관리할지의 판단이다.
핵심 개념
100%는 목표가 될 수 없다
품질과 신뢰성 판단이 어려운 근본 이유는 완벽이 불가능해서가 아니라, 완벽에 가까워질수록 비용이 비선형으로 커지는데 얻는 가치는 그렇지 않다는 데 있다.
검증 쪽의 한계. Dijkstra가 지적했듯 테스트는 버그의 존재를 보일 수 있을 뿐 부재를 증명하지 못한다. 주문 확정 API의 입력 공간만 해도 상품 조합, 동시 요청 순서, 하위 서비스의 지연·실패 타이밍이 조합 폭발을 일으켜 전수 검사가 원리적으로 불가능하다. TLA+ 같은 형식 검증(formal verification)은 모델이 표현한 범위 안에서 부재를 증명할 수 있지만, 명세 자체가 틀릴 수 있고 모델과 구현의 일치는 별도 문제로 남는다. 검증은 언제나 부분적이며, 그래서 어떤 부분을 검증할지가 판단의 대상이 된다.
운영 쪽의 한계. 가용성 한 자리(9 하나)를 올릴 때마다 비용은 대략 자릿수 단위로 커진다. 이중화, 다중 리전, 자동 장애 복구가 차례로 필요해지고, 무엇보다 변경 자체가 위험이므로 변경 속도를 늦춰야 한다. 반면 사용자가 체감하는 가치는 어느 지점부터 늘지 않는다. 사용자의 요청은 ISP, Wi-Fi, 단말을 거치며 이미 일정 확률로 실패하고 있어서, 서비스가 그 배경 잡음보다 훨씬 더 신뢰적이 되어도 사용자는 구분하지 못한다. 30일 기준으로 허용 중단 시간을 계산하면 목표의 무게가 드러난다.
| 가용성 목표 | 30일 중 허용 실패 시간 | 의미 |
|---|---|---|
| 99% | 7.2시간 | 야간 점검 창이 존재할 수 있는 수준 |
| 99.9% | 43.2분 | 장애 한 번을 사람이 대응할 여유가 있는 수준 |
| 99.99% | 4.3분 | 사람의 대응이 불가능하고 자동 복구가 전제되는 수준 |
| 99.999% | 26초 | 배포·설정 변경 자체를 극도로 제한해야 하는 수준 |
99.99%를 목표로 선언하는 순간 "장애가 나면 담당자가 확인한다"는 운영 방식 전체가 무효가 된다. 4.3분은 사람이 알림을 보고 노트북을 여는 시간보다 짧다. 목표 수치는 취향이 아니라 운영 체계 전체에 대한 요구사항이다.
품질은 위험에 대한 증거다
완벽한 검증이 불가능하다면, 각 검증 수단은 "정확함의 증명"이 아니라 특정 위험을 줄이는 증거를 특정 비용에 사는 거래로 이해해야 한다. 수단마다 사는 것과 사지 못하는 것의 경계가 다르다.
| 수단 | 사는 증거 | 사지 못하는 것 | 주요 비용 |
|---|---|---|---|
| 단위 테스트 | 격리된 로직이 명시된 예제·불변식을 지킨다 | 구성 요소 사이의 계약, 실제 의존성의 동작 | 작성, 구조 결합으로 인한 유지 비용 |
| 통합 테스트 | 구성 요소 조합이 실제 의존성과 함께 동작한다 | 프로덕션 규모·데이터 분포에서의 동작 | 환경 구축, 느린 실행, 불안정성 |
| E2E 테스트 | 사용자 여정이 전체 시스템을 관통해 성공한다 | 실패 원인의 위치, 경계 조건의 폭넓은 커버 | 가장 느린 실행, 가장 높은 유지·불안정 비용 |
| 코드 리뷰 | 가독성·유지보수성, 설계 맥락의 공유 | 실행해야 드러나는 깊은 기능 결함 | 리뷰어 시간, 대기 지연 |
| 정적 지표 | 검증 활동의 공백(실행되지 않은 코드, 복잡도 집중) | 실행된 코드가 올바른지 여부 | 수집은 싸지만 오독·왜곡 비용이 큼 |
| 프로덕션 SLI | 실제 사용자·데이터·규모에서의 동작 | 사고를 사전에 막는 능력(이미 노출된 뒤 관측) | 계측 구축, 실 사용자 일부의 피해 |
이 표에서 두 가지 판단 원칙이 나온다. 첫째, 싼 증거부터 배치한다. 단위 테스트가 잡을 수 있는 위험을 E2E로 잡는 것은 같은 증거를 수십 배 비용으로 사는 것이다. 둘째, 각 증거의 경계를 기록한다. "테스트 통과"가 무엇을 보장하고 무엇을 보장하지 않는지 팀이 알아야, 통과한 배포가 장애를 내도 "테스트가 있는데 왜"라는 잘못된 질문 대신 "이 위험은 어떤 증거로 덮을 것인가"라는 다음 판단으로 넘어갈 수 있다.
신뢰성은 실패의 예산이다
사전 증거를 아무리 쌓아도 위험은 남는다. 남은 위험은 부정하는 것이 아니라 예산으로 관리한다. 신뢰성 목표(SLO)를 99.9%로 합의했다면 나머지 0.1%는 실패해도 되는 양, 즉 에러 버짓(error budget)이다. 30일에 43.2분어치의 실패는 장애로 소진될 수도 있고, 위험한 변경을 시도하는 데 투자될 수도 있다.
예산이라는 프레임이 중요한 이유는 신뢰성과 변경 속도의 갈등을 협상 가능한 수치로 바꾸기 때문이다. 예산이 남아 있으면 공격적으로 배포하고, 소진되면 기능 배포를 멈추고 신뢰성 작업에 투자한다. "안정성이 중요하니 배포를 줄이자"는 힘의 논쟁이 "이번 달 예산의 80%를 썼으니 남은 기간은 위험한 변경을 미루자"는 계산으로 바뀐다.
같은 구조가 코드 품질에도 적용된다. 기술 부채는 청산해야 할 죄가 아니라 이자(변경 비용 증가)를 내는 대출이다. 자주 변경되는 코드의 부채는 이자가 비싸므로 갚고, 변경되지 않는 코드의 부채는 안고 간다. 품질도 신뢰성도 절대 수준이 아니라 위험·비용·속도 사이의 명시적 거래로 다루는 것이 이 챕터의 일관된 관점이다.
챕터의 판단 루프
세 문서는 대상이 다르지만 같은 판단 루프를 공유한다.
1. 지키려는 불변식·품질 목표를 검증 가능한 형태로 명시한다
│
▼
2. 위험을 줄일 증거 수단의 비용과 신뢰도를 비교한다
│
▼
3. 싼 증거부터 배치하고, 각 증거가 보장하는 것과
보장하지 못하는 것의 경계를 기록한다
│
▼
4. 남은 위험을 예산으로 관리하고, 소진 신호를
배포 중단·상환 우선순위 같은 행동에 연결한다
│
└──── 운영 결과가 목표·증거 배치를 수정 ────▶ 1로챕터 지도
- 테스트 전략은 테스트 수준을 비용×신뢰도 트레이드오프로 재구성하고, 테스트 대역이 늘수록 잃는 보장, 레거시 코드에 테스트를 넣는 특성화 테스트와 seam, 속성 기반 테스트, flaky의 구조적 원인, 커버리지가 보장하지 않는 것을 뮤테이션 테스트로 관찰하는 방법을 다룬다.
- 품질과 유지보수성은 코드 리뷰가 실제로 잡는 결함의 연구 증거, 정적 지표가 목표가 될 때의 왜곡, git 로그 기반 핫스팟으로 기술 부채를 계측해 상환을 이자로 판단하는 모델을 다룬다.
- 신뢰성 공학은 좋은 SLI의 조건, SLO와 에러 버짓의 운영적 의미, burn rate 경보 설계, 장애 대응과 포스트모템, 점진적 배포가 에러 버짓을 지키는 메커니즘을 다룬다.
품질 요구사항을 도출하고 품질 속성 시나리오로 명세하는 방법은 챕터 13이 담당하며 여기서는 정의된 목표를 검증·측정한다. CI/CD 파이프라인 구축과 관찰 가능성 스택은 챕터 15, 부분 실패·재시도·멱등성의 분산 원리는 챕터 10에서 다룬다. 정적 분석이 결함을 찾는 원리는 챕터 5의 정적 분석을 전제하고 여기서는 지표의 운영적 의미만 다룬다.
실무 관점: 절대값 사고가 만드는 함정
"커버리지 90% 달성"이 목표가 될 때. 커버리지는 검증되지 않은 코드를 찾는 증거로는 유용하지만, 목표가 되는 순간 단언(assertion) 없는 테스트로도 달성할 수 있는 숫자가 된다. 지표가 재는 것(실행 여부)과 원하는 것(결함 검출력)의 간극은 14.1에서 뮤테이션 테스트로 직접 관찰한다.
"최대한 안정적으로"가 목표일 때. 수치 없는 신뢰성 목표는 모든 장애를 최우선으로 만들어 우선순위를 없앤다. 새벽 3시에 사람을 깨울 가치가 있는 문제와 다음 날 처리해도 되는 문제를 구분하는 기준이 없으면, 경보 피로가 쌓이고 정작 중요한 신호가 묻힌다.
품질 활동을 일정의 버퍼로 취급할 때. 마감이 다가오면 테스트와 리뷰부터 줄이는 팀은 증거 없는 변경의 위험을 미래로 이월하는 것이다. 이월된 위험은 사라지지 않고 장애 대응 시간과 변경 비용 증가라는 이자로 돌아온다. 판단 없이 줄이는 것과, 위험이 낮은 영역의 증거를 의도적으로 늦게 사는 것은 다르다. 후자는 전략이고 전자는 부채의 무계획 차입이다.
"로컬에서 됐다"로 확신할 때. 개발 환경의 성공은 특정 데이터·타이밍·환경에서의 증거 한 조각일 뿐이다. 어떤 조건이 프로덕션과 다른지(데이터 규모, 동시성, 의존 서비스의 지연 분포)를 말할 수 없다면 그 증거의 경계를 모르는 것이다.
정리
- 검증은 원리적으로 부분적이고 신뢰성의 한계 비용은 비선형으로 커지므로, 품질과 신뢰성은 절대값이 아니라 위험·비용·속도의 거래로 다룬다.
- 각 검증 수단은 특정 위험에 대한 증거를 특정 비용에 산다. 싼 증거부터 배치하고 각 증거의 보장 경계를 기록한다.
- 신뢰성 목표는 100%가 아니라 합의된 수치이고, 목표와 100%의 차이는 장애와 위험한 변경에 쓸 수 있는 에러 버짓이다.
- 예산 프레임은 신뢰성 대 변경 속도의 갈등을 힘의 논쟁에서 수치 협상으로 바꾼다. 소진 신호는 배포 중단·상환 같은 행동에 연결되어야 의미가 있다.
- 세 문서는 불변식 명시 → 증거 비교·배치 → 경계 기록 → 예산 관리라는 같은 판단 루프를 테스트, 코드 품질, 운영 신뢰성에 적용한다.
확인 문제
- 어떤 팀이 "모든 배포는 E2E 테스트 전체 통과 후에만 나간다"를 유일한 품질 게이트로 운영한다. 이 정책이 사는 증거와 사지 못하는 증거를 구분하고, 이 팀이 겪을 가능성이 높은 문제 두 가지를 예측하라.
- 경영진이 "경쟁사가 99.99%를 광고하니 우리도 99.99%로 올리자"고 요구한다. 수용 여부를 판단하기 위해 확인해야 할 사실을 세 가지 이상 제시하라.
- 배경의 주문 확정 API 사례에서, 팀이 추가 투자 여력이 한정되어 사전 증거(느린 의존성을 주입하는 통합 테스트)와 사후 예산(카나리 배포 + SLI 자동 롤백) 중 하나만 먼저 구축할 수 있다면 무엇을 근거로 선택해야 하는가?
정답과 해설
- E2E 통과는 "검증한 사용자 여정이 전체 시스템을 관통해 성공한다"는 증거를 산다. 사지 못하는 것은 경계 조건의 폭넓은 커버(느린 실행 때문에 시나리오 수가 제한된다), 실패 시 원인의 위치, 부분 실패·동시성 같은 재현이 어려운 조건이다. 예측되는 문제: 첫째, E2E의 불안정성(flaky)이 배포 파이프라인 전체를 막아 재실행이 관행이 되고 실제 실패 신호가 묻힌다. 둘째, 결함의 원인 위치를 알려주지 않으므로 실패마다 진단 비용이 크고, 더 싸게 잡을 수 있는 로직 결함까지 가장 비싼 수단으로 잡게 되어 피드백이 느려진다.
- 첫째, 현재 실측 가용성과 목표의 격차. 이미 99.99%를 내고 있다면 선언 비용뿐이지만 99.5%라면 운영 체계 전환이 필요하다. 둘째, 30일 4.3분이라는 허용치가 요구하는 운영 능력(자동 복구, 사람 개입 없는 롤백)에 대한 투자 의사. 셋째, 의존하는 하위 서비스·인프라의 가용성. 의존성이 99.9%라면 그 위에서 99.99%를 내는 것은 의존성을 우회하는 재설계를 뜻한다. 넷째, 사용자가 그 차이를 체감하는지. 사용자 경로의 배경 실패율이 더 높다면 추가 투자의 가치가 없다. 경쟁사의 광고 수치는 SLA(계약상 보상 기준)일 가능성이 높고 내부 SLO와 다를 수 있다는 점도 확인 대상이다.
- 두 수단이 덮는 위험의 범위와 실패 비용을 비교해야 한다. 통합 테스트는 "명시한 가정"(재시도 횟수, 타임아웃 예산)의 위반만 잡지만 배포 전에 잡는다. 카나리+SLI는 명시하지 못한 미지의 위험까지 잡지만 사용자 일부가 피해를 본 뒤에 잡는다. 판단 근거는 (1) 위험의 성격 — 이 장애처럼 의존성 변경·환경 상호작용에서 오는 위험은 사전에 열거하기 어려우므로 사후 수단의 커버가 넓다. (2) 실패의 되돌림 비용 — 중복 주문처럼 데이터 정합성을 깨는 실패는 롤백 후에도 정리 비용이 남으므로 사전 차단의 가치가 크다. (3) 재사용성 — 카나리·SLI 기반은 이후 모든 배포에 적용되는 기반 투자다. 일반적으로는 미지의 위험까지 덮고 모든 변경에 재사용되는 카나리+SLI가 먼저지만, 중복 주문 같은 정합성 파괴가 반복될 상황이라면 해당 불변식의 사전 검증을 먼저 살 근거가 된다.
참고 자료
- Marc Alvidrez, Embracing Risk (Google SRE Book) — 100%가 목표가 될 수 없는 이유, 비용·체감의 비선형성, 에러 버짓의 원 정의를 확인한다.
- Edsger W. Dijkstra, Notes on Structured Programming (EWD249) (1970) — "테스트는 버그의 존재를 보일 수 있을 뿐 부재를 보일 수 없다"는 검증의 원리적 한계의 출처다.
- ISO/IEC 25010:2023 — 품질을 단일 수치가 아니라 특성(신뢰성, 유지보수성, 보안 등)의 모델로 다루는 표준 용어를 개요 수준에서 참고한다.
- Betsy Beyer 외, Service Level Objectives (Google SRE Book) — SLI·SLO·에러 버짓 용어의 원 정의로, 14.3의 전제가 되는 문서다.