Common failure causes
- 14.1 Hardware faults
- 14.2 Incorrect error handling
- 14.3 Configuration changes
- 14.4 Single points of failure
- 14.5 Network faults
- 14.6 Resource leaks
- 14.7 Load pressure
- 14.8 Cascading failures
- 14.9 Managing risk
장애 내성 애플리케이션을 만들려면 먼저 뭐가 잘못될 수 있는지 알아야 하잖아. 시스템을 무너뜨리는 건 이국적인 장애가 아니라 아주 일상적인 것들이야.
HDD, 메모리, 전원, NIC, CPU — 물리적 부품은 다 고장나. 정전이나 자연재해로 데이터센터 전체가 다운될 수도 있지. 근데 실제로 분산 애플리케이션을 죽이는 건 훨씬 더 흔한 것들이야. 2014년 연구에서 5개 인기 분산 데이터 스토어의 장애를 분석했는데, 치명적 장애의 대다수가 비치명적 에러의 잘못된 처리 때문이었어. 에러를 완전히 무시하는 핸들러, Exception 같은 과도하게 넓은 예외를 잡고 프로세스 전체를 중단시키는 코드, FIXME 달린 반쯤 구현된 핸들러 — 대부분 단순한 테스트로 잡을 수 있었던 버그들이지.
설정 변경도 치명적 장애의 주요 원인이야. 특히 위험한 건 효과가 지연될 수 있다는 거거든. 애플리케이션이 설정 값을 실제 필요할 때만 읽으면, 잘못된 값이 시간이나 며칠 뒤에 비로소 적용돼서 조기 감지를 피해. 그래서 설정 변경도 코드 변경처럼 버전 관리, 테스트, 릴리스되어야 해. **SPOF(단일 장애점)**는 실패하면 전체 시스템을 무너뜨리는 컴포넌트야. 의외의 SPOF들이 있어 — 특정 순서의 수동 운영 단계를 실수 없이 해야 하는 사람, 만료될 수 있는 DNS나 TLS 인증서 같은 것들. 모든 컴포넌트를 하나씩 짚으며 "이게 죽으면 어떻게 되지?" 자문하는 게 SPOF 찾는 최선의 방법이야.
느린 네트워크 호출은 분산 시스템의 silent killer야. 응답이 올지 안 올지 모르니까 클라이언트가 오래 대기하면서 디버깅하기 어려운 성능 저하를 일으켜. 이런 걸 gray failure라 불러 — 너무 미묘해서 빠르게 감지할 수 없는 장애지. 리소스 누수도 조용한 살인자야. 메모리 누수(GC 언어도 안전하지 않아), 타임아웃 없는 동기 호출로 인한 스레드 풀 고갈, 비동기 호출의 소켓 풀 고갈 — 자기 코드뿐 아니라 의존 라이브러리도 같은 문제를 일으킬 수 있어.
모든 시스템에는 용량(capacity) 한계가 있어. 계절성, 스크래퍼, DDoS — 오토스케일링으로 안 되면 요청을 거부해야 하는 경우도 있지. 그리고 결함은 바이러스처럼 전파돼. 로드 밸런서 뒤에 복제본 A, B가 각 50 TPS를 처리하다 B가 다운되면 A가 100 TPS를 감당해야 하고, A도 타임아웃되고, 클라이언트 재시도로 더 많은 부하가 밀려오고, A도 다운되고, B가 복구돼도 혼자 전체 부하를 받아 또 다운돼. 이런 metastable failure는 원래 결함이 사라진 뒤에도 피드백 루프 때문에 복구가 안 돼. 최선은 처음부터 결함 전파를 막는 것이야.
모든 결함에 대응할 필요는 없어. 각 결함의 발생 확률 x 영향 = 리스크 점수로 우선순위를 매기면 돼. 대응은 확률을 줄이거나 영향을 줄이는 두 축이야.
정리
14장 읽고 기억할 거 세 가지:
- 시스템을 무너뜨리는 건 이국적 장애가 아니라 에러 처리 누락, 설정 변경의 지연 효과, 리소스 누수 같은 평범한 것들이야.
- Metastable failure — 원래 결함이 사라져도 피드백 루프가 시스템을 복구 불가 상태에 가둬. 시작되면 큰 교정 조치가 필요하고, 최선은 애초에 결함 전파를 막는 거지.
- 모든 결함에 대응할 필요 없어 — 확률 x 영향 = 리스크 점수로 우선순위를 매기고, 확률이나 영향을 줄이는 방향으로 집중하면 돼.