결함·오류·실패을 분리해 장애 원인을 추적하는 방법
결함·오류·실패의 인과사슬을 분리하고, 서비스 경계의 관측·격리·복구 설계에 적용하는 신뢰성 공학 실무 가이드
2026-08-14 · 최초 발행 2025-12-20
장애 분석의 출발점은 원인과 증상을 섞지 않는 데 있다
소프트웨어 신뢰성 공학(Dependability Engineering)에서 Error-Fault-Failure 구분은 사건을 해석하는 표준 어휘 체계다. Avizienis-Laprie 분류 체계를 바탕으로 원인, 내부 증상, 외부 결과를 나누면 사고 대응·테스트 전략·관측가능성 설계를 같은 기준으로 맞출 수 있다.
결함(Fault)은 시스템 안팎에서 유입되는 결함 요인이다. 설계 결함, 구현 버그, 구성 오류, 하드웨어 결함, 운영상 실수, 외부 환경 교란이 여기에 포함된다. 결함은 잠복(latent) 상태로 있다가 특정 활성화 조건에서 동작하며, 일시적(transient)·간헐적(intermittent)·영구적(permanent), 내부·외부, 인적·물리적 관점으로도 분류한다.
오류(Error)는 올바른 상태에서 벗어난 내부 시스템 상태다. 잘못된 메모리·캐시·인덱스·세션·트랜잭션 상태가 예가 된다. 오류는 다른 컴포넌트로 전파될 수도 있고 masking이나 containment를 통해 소거될 수도 있다. 탐지하지 못한 오류만이 외부 실패로 이어질 위험을 갖는다.
실패(Failure)는 서비스 경계(Service Boundary)에서 제공한 서비스가 사양과 다르게 관측되는 결과다. 5xx 응답, 잘못된 응답 바디, SLO 위반, 데이터 무결성 파손이 이에 해당한다. 고객이나 외부 모니터가 인지할 수 있는 이상이며, 내부 오류가 경계를 넘어온 사건이다.
인과사슬은 결함 활성화에서 시작해 오류 상태 발생, 오류 전파, 시스템 경계 도달, 실패 관측으로 이어진다. RCA에서는 반대로 실패에서 오류를 거쳐 결함까지 추적한다.
서비스 경계에서 실패를 정의하고 내부 상태를 따로 본다
실패는 API, 메시지 큐, UI, 데이터 계약처럼 시스템 경계에서 정의된다. 내부 오류가 경계에 닿지 않았다면 실패는 아니다. 따라서 로그·메트릭·트레이싱은 경계 전후 상태를 구분해 수집하고, 실패 지표는 고객 영향 중심으로 설계할 필요가 있다.
같은 결함이라도 트래픽 패턴, 데이터 분포, 동시성 타이밍, 특정 구성에 따라 활성화 여부가 달라진다. 재현성을 확보하려면 입력·환경·타이밍 제약을 기록해야 한다. 영구·간헐·일시 결함은 탐지와 시험 방식도 다르므로 카나리아와 퍼센트 롤아웃을 활용할 수 있다.
오류는 컴포넌트 경계를 넘나들며 전파된다. 대용량 시스템에서는 이 전파가 폭포수처럼 번져 전체 서비스 실패로 이어질 수 있다. 벌크헤드, 회로차단기, 타임아웃, 격리 큐, 멱등성, 사가 보상 트랜잭션은 전파를 끊기 위한 장치다.
입력 검증, 어서션, 가드레일 정책, 헬스체크, 회귀 테스트는 오류를 선제적으로 찾는 수단이다. 오류가 확인된 뒤에는 롤백·리트라이·폴백·격리·리스타트로 대응하고, 이후 RCA·재발 방지 변경·계측 추가로 결함 제거 활동을 이어간다.
이 분류는 경보 라우팅, 티켓 템플릿, 포스트모템 구조를 표준화하는 기준이 된다. 분류 정합성이 높아지면 MTTA와 MTTR 단축에 도움이 되며, 테스트 포트폴리오와 카오스 실험을 설계하는 기준도 제공한다.
장애 대응에서는 실패에서 결함까지 거슬러 올라간다
캐시 키 충돌로 잘못된 객체가 반환되어 일관성이 일탈하고, 고객 3.2%의 5xx 응답 증가와 SLI 위반이 관측됐다고 가정할 수 있다. 이때 고객 영향은 실패, 잘못된 객체 반환은 오류, 해시 함수 변경 시 솔트 누락이나 구성 플래그 불일치는 결함이다. 조치는 롤백과 구성 고정으로 시작하고, 재배포 파이프라인에는 스키마 체크를 넣는다.
테스트도 어느 단계에 대응하는지 분명히 할 수 있다. 정적 분석, 린트, 계약 기반 설계, 형식 검증은 결함 예방에 가깝다. 속성 기반 테스트, 비정상 입력 퍼징, 동시성 레이스 탐지는 오류 검출을 겨냥한다. 카오스·결함 주입 실험, 장애 복구 게임데이, 카나리아 모니터링은 실패를 리허설하는 수단이다.
관측가능성은 실패와 오류, 결함의 신호를 같은 지표로 뭉치지 않는다. 성공율·지연·데이터 정확성은 실패 중심 SLO/SLI로 다루고, 재시도율·예외율·롤백 건수는 내부 오류를 찾는 지표로 둔다. 빌드 차이, 구성 드리프트, 노드 하드웨어 오류는 결함 신호다.
벌크헤드는 리소스를 격리하고, 회로차단기와 타임아웃은 오류 전파를 막는다. 멱등키와 사가는 데이터 레벨에서 실패를 복구하며 재진입과 중복 처리의 안전성을 확보한다.
탐지와 격리가 실패로 번지는 경로를 끊는다
가드와 계측을 추가해 내부 오류를 지표화하면 에러 탐지율은 +20~40%p 개선이 기대된다. 벌크헤드와 타임아웃을 도입한 조직에서는 오류→실패 전파율이 30% 이상 감소한 사례도 다수 있다.
분류를 표준화해 라우팅과 의사결정 지연을 줄이면 MTTA는 1530%, MTTR은 2040% 단축될 수 있다. 포스트모템에서 결함-오류-실패 연결형 액션 아이템을 적용하면 재발률도 25% 이상 감소할 수 있다.
동일한 결함 밀도에서도 고객이 관측하는 실패율은 0.1~0.5%p 절감되고 SLO 위반 시간도 줄어들 수 있다. 경보 품질이 개선되면 알람 피로도가 낮아지고 온콜 만족도는 높아진다. 지표는 시스템·트래픽·조직 성숙도에 따라 변동하므로 주기적 측정과 회귀 방지체계가 필요하다.
오류가 경계를 넘기 전의 처리 경로
입력·처리·출력 단계마다 오류 검출 지점을 둔다. 검출하지 못한 오류는 시스템 경계에 도달해 실패로 관측되지만, 탐지와 격리에 성공하면 경계 이전에서 소거할 수 있다. RCA 결과는 다시 결함 제거 활동으로 환류한다.
관점에 따라 달라지는 영향과 운영 방식
| 구분 | 성능 영향 | 확장성 영향 | 일관성 영향 | 안정성 영향 | 운영 편의성 |
|---|---|---|---|---|---|
| 결함(Fault) | 직접 영향 낮음(잠복 시), 예방 활동은 빌드 시간 증가 | 코드/구성 복잡도로 간접 제약 가능 | 스키마·계약 결함은 전면적 위험 | 활성화 시 대량 장애 잠재 | 탐지 어려움, 리뷰·정적 분석 필요 |
| 오류(Error) | 재시도·롤백 비용으로 지연 증가 | 폭주 재시도 시 병목·스톰 유발 | 트랜잭션 충돌·더티 리드 등 상태 일탈 | 전파 시 서비스 불안정 | 예외율·가드로 관측 가능, 튜닝 용이 |
| 실패(Failure) | SLA 관점 성능 저하로 직결 | 장애 도메인 확장 시 고객 영향 확대 | 외부에 잘못된 결과 확정 | 신뢰도 하락, 에러버짓 소모 | 탐지 용이, 원인 역추적은 난이도 높음 |
코드에서 보는 경계 이전 오류 소거
환경과 전제조건은 Python 3.11이며, 외부 프레임워크 없이 표준 라이브러리만 사용한다. 예시는 None 처리 누락이라는 구현 버그가 입력의 None 값으로 활성화되고 TypeError라는 오류를 만들 수 있음을 보인다. 입력 검증과 폴백은 이 오류를 서비스 경계 이전에서 소거해 실패 전파를 막는다.
from typing import Any, Dict, Tuple
# 결함(Fault): None 처리 누락
def apply_tax(amount: float, tax_rate: float) -> float:
# 버그: amount나 tax_rate가 None일 때 예외 발생
return amount * (1.0 + tax_rate)
# 경계 레벨 핸들러: 실패(Failure) 방지 목적의 오류(Error) 소거
def handle_request(payload: Dict[str, Any]) -> Tuple[int, Dict[str, Any]]:
# 1) 입력 검증으로 오류 사전 차단
if not isinstance(payload.get("amount"), (int, float)):
return 400, {"error": "invalid_amount"}
if not isinstance(payload.get("tax_rate"), (int, float)):
return 400, {"error": "invalid_tax_rate"}
# 2) 방어 로직: 범위 가드와 폴백
amount = float(payload["amount"])
tax_rate = float(payload["tax_rate"])
if not (0.0 <= tax_rate <= 0.5): # 비즈 규칙 예시: 세율 0~50%만 허용
return 422, {"error": "tax_rate_out_of_range"}
try:
total = apply_tax(amount, tax_rate) # 오류 발생 가능 지점
except TypeError:
# 오류(Error) 탐지 → 실패(Failure) 전파 차단
return 500, {"error": "internal_error_contained"}
# 3) 정상 응답
return 200, {"total": round(total, 2)}
if __name__ == "__main__":
# 활성화 조건 충족 케이스 → 검증으로 차단
print(handle_request({"amount": None, "tax_rate": 0.1})) # (400, ...)
# 정상 케이스
print(handle_request({"amount": 100, "tax_rate": 0.1})) # (200, {"total": 110.0})
결함이 존재해도 경계에서 오류를 탐지하고 격리하면 실패를 예방할 수 있다. 검증 실패를 4xx로 매핑하면 고객에게 통제된 결과를 제공하고 서버 실패 5xx를 최소화한다.
분류 체계를 운영 절차에 녹이는 방식
티켓과 포스트모템 템플릿에 결함·오류·실패 필드를 고정하고, RCA 액션은 결함 제거에 집중한다. 문서화 부담은 늘지만 재현·학습·자동화는 쉬워진다.
관측가능성도 계층을 나눈다. 성공률·지연·정확성은 SLI/SLO 중심의 실패 지표로, 예외율·롤백·리트라이·타임아웃은 오류 지표로 관리한다. 계측 오버헤드와 진단 속도 사이에서는 샘플링·압축·레벨링으로 균형을 잡는다.
타임아웃, 회로차단기, 벌크헤드, 큐잉, 백프레셔와 데이터 레이어의 멱등성·사가는 오류 전파를 차단하는 보호장치다. 지연과 복잡성은 증가할 수 있지만 시스템 안정성은 높아진다.
정적 분석과 계약 테스트는 결함 예방, 퍼징과 동시성 테스트는 오류 검출, 카오스와 결함 주입은 실패 리허설에 배치한다. 테스트 시간 증가는 배포 안정성 향상과 롤백 비용 절감이라는 대가와 함께 검토한다.