알아야 할 실패 개념 세 가지
환영
분산 시스템은 패턴으로 실패합니다. 패턴을 배우면, 모든 포스트모템이 인식 연습이 아닌 미스터리가 됩니다.
다른 개념은 대부분의 중요한 생산 실패 분석을 커버합니다:
단일故障점(SPOF): 시스템 전체가 멈추게 되는 구성 요소. 종종 숨겨져 있습니다: 모든 사람이 의존하는 DNS 서버; 모든 것이 갱신하는 대신 사용하는 인증서; 단일 데이터베이스 마스터.
연쇄적 실패: 한 구성 요소의 실패가 다른 구성 요소의 실패를 유발하고, 그 다음의 실패를 유발합니다. 느린 데이터베이스는 API 계층의 타임아웃을 유발하고, 이는 다시 시도하여 데이터베이스 부하를 증가시키고, 이는 다시 타임아웃을 유발합니다. 폭스가 확산됩니다.
폭발 반경: 한 부분이 실패할 때 시스템의 어떤 부분이 멈추는가. 아키텍처 선택은 폭발 반경을 바인딩하거나 확장하는 데 영향을 줍니다. SPOF는 폭발 반경이 무제한입니다. bulkheaded 서비스는 폭발 반경이 제한됩니다.
이 강의를 마치면:
- 아키텍처를 검사하여 SPOF를 식별할 수 있습니다.
- 연쇄적 실패 패턴을 인식합니다: 천둥 소총, 시도 폭풍, 큐의 죽음
- 실제 타임라인을 읽고 트리거와 트리거가 표출한 잠재적 결함을 구분합니다
- 시스템 대신 사람을 대상으로 하는 비책임스 액션 아이템을 작성할 수 있습니다. 예방 / 탐지 / 복구를 포함합니다.
- bulkheads & 서킷 브레이커를 폭발 반경을 제한하는 도구로 理解할 수 있습니다.
SPOF를 찾아내세요
계층적 아키텍처 검사
작은 웹 아키텍처를 고려하십시오:
- DNS: api.example.com -> 단일 네임서버 IP 203.0.113.10 단일 DNS 제공업체에 의해 호스팅
- CDN: api.example.com 앞에 단일 CDN 제공업체
- 인가: 로드 밸런서 뒤에 두 개의 역방향 프록시 머신
- 백엔드: 두 가용성 영역 (영역당 세 개)에서 여섯 개의 API 복제본
- 데이터베이스: 동일한 가용성 영역에 있는 단일 주(primary) + 단일 읽기 복제본
- 캐시: 세 개의 노드가 동일한 두 가용성 영역에 걸쳐 분산된 Redis 클러스터
질문: SPOF는 단일 머신이 아닌 경우도 있습니다. Availability Zone의 실패에 대한 클러스터 세 개의 머신은 해당 영역의 실패로 SPOF입니다. 힌트:
세 가지 기본 연쇄 패턴
실패가 의존성 через 전달
패턴 1: 폭풍의 무리. 공유 자원(캐시, 락, 데이터베이스)이 실패하거나 재시작합니다. 그에 의존하던 모든 클라이언트가 동시에 재시도합니다. 폭풍은 다시 시작되는 것보다 더 빨라 재시도가 쌓인다; 회복이 절대로 완료되지 않습니다.
패턴 2: 재시도 폭풍. 하위 서비스가 느려집니다. 상위 호출자가 실패하지 않고 재시도합니다. 재시도가 원래 로드를 여러 배로 늘립니다. 느린 서비스가 더 느려져 더 많은 재시도를 유발합니다. 결국 로드는 건강한 버전의 서비스까지도 넘어갑니다.
패턴 3: 죽음의 큐. 백프레셔가 없는 처리 큐가 더 빠르게 받습니다. 큐가 무한정 커집니다. 메모리가 소진되어 소비자가 충돌합니다; 다시 시작; 더 큰 큐를 찾고 다시 충돌합니다.
공통된 주제: 작은 초기 간섭이 양적 피드백 루프를 유발합니다. 시스템의 반응이 실패를 확대하는 대신 진동시키는 것을 줄입니다.
진동 조절 기구
지수 백오프와 지터. 재시도하는 클라이언트는 더 긴 시간을 기다립니다. 무작위로 오프셋이 있습니다. 동기화된 재시도 파도를 방지합니다.
서킷 브레이커. 호출자가 하위 실패율을 추적합니다. 임계점을 넘어서 호출자가 멈추고 쿨다운 기간 동안 호출을 중단하며 즉시 실패한 요청을 제공합니다. 하위에서 회복할 수 있도록 비용을 아낍니다.
배수대. 의존성별로 자원을 고립시킵니다. 데이터베이스 연결 풀 A, 캐시 연결 풀 B. 느린 데이터베이스가 모든 연결을 배불리게 할 수 없습니다; 캐시 호출은 계속됩니다.
부하 감소. 과부하일 때 에지에서 요청을 무력화시키는 것이 점진적으로 실패하는 것보다 낫습니다. 429 in 1 ms는 500 in 30 seconds보다 낫습니다.
백프레셔. 소비자가 처리할 수 없을 때 생산자를 늦춥니다. 큐가 유한해집니다; 보내는 사람을 차단합니다; 원래 작업의 근원은 마찰을 느끼게 됩니다.
연쇄 진단
팀의 API 계층이 정기 데이터베이스 실패오버 중에 멎게 된다. 타임라인:
- 14:00:00 — 작업자가 대기 데이터베이스를 승격시킴. 예상 불가용성: ~10초.
- 14:00:08 — 주 데이터베이스 불가용. API 계층에서 데이터베이스 연결 오류로 요청이 실패 시작.
- 14:00:08 — API 계층 재시도(기본 구성: 5번 재시도, 백오프 없음, 100ms 간격).
- 14:00:11 — 대기 중인 데이터베이스 승격, 새로운 연결 수락.
- 14:00:11 — API 계층이 수천 개의 새로운 데이터베이스 연결을 동시에 연다(각 레플리카 × 각 동시 요청 × 각 재시도).
- 14:00:13 — 새로운 주 데이터베이스의 연결 풀이 고갈되어; 새로운 연결 거절.
- 14:00:13-14:05:00 — API 계층 레플리카 연결 풀이 고갈되어, 예외를 던지며, 재시작, 반복.
- 14:05:00 — 작업자가 API 계층 트래픽을 수동으로 중지; 데이터베이스 안정화.
- 14:10:00 — 점진적 트래픽 복구 완료. 총 장애 시간: ~10분(예상 시간: ~10초).
DNS SERVFAIL: 두 가지 복합적 결함
실제 모양의 포스트모템
다음은 실제 사고의 비정규 버전입니다. 벤더 이름 변경, IP 주소 익명화; 모양, 타임라인, 그리고 교훈은 실제입니다.
요약
사이트 example.com은 모든 공개 DNS 리졸버에서 SERVFAIL을 반환했으며, 약 3-4시간 동안 지속되었습니다. 다른 46개 영역은 동일한 DNS 마스터에 영향을 받지 않았습니다. 원인: 두 가지 복합적 결함.
1. 벤더 A(보조 DNS 제공업체)는 주 데이터베이스의 allow-axfr-ips 허용 목록에 없는 새로운 내부 동기 IP를 추가했습니다.
2. example.com 영역은 몇 년 동안 RFC 위반 CNAME 충돌(demo.example.com이 동일 레이블에서 CNAME 및 MX/TXT 레코드를 모두 가지고 있음)이 있었으며, 새로운 AXFR을 거부하는 벤더 A를 유발했습니다.
타임라인(UTC)
- ~15:00 — 벤더 A가 인프라에 새로운 동기 IP 198.51.100.42를 추가함
- 15:02 — first AXFR-out denied for 198.51.100.42 appears in primary DNS logs (no alerting on this signal)
- ~18:00 — SOA expire window reached; Vendor A drops example.com zone from cache
- ~18:30 — SERVFAIL detected externally
- ~19:45 — root cause identified
- 20:00 — 198.51.100.42 added to allow-axfr-ips; primary restarted
- 20:05 — NOTIFY sent; AXFR initiated; zone STILL SERVFAIL (CNAME conflict)
- 20:07 — check-zone reveals 1 error: CNAME conflict on demo.example.com
- 20:09 — CNAME replaced with A record; zone check clean (0 errors)
- 20:10 — NOTIFY sent; AXFR completes; Vendor A begins serving zone
- 20:11 — dig @8.8.8.8 example.com A returns correct IP — RESOLVED
Why Only example.com?
All 47 zones share the same DNS primary. The AXFR IP block affected all zones. But only example.com had the CNAME conflict, & only example.com needed a fresh AXFR at the moment the deny was enforced. Other zones had already refreshed before the deny or did not yet need to.
Latent defect
The CNAME conflict at demo.example.com had existed for years. It worked because the primary served the zone from its database (lenient about RFC violations) & Vendor A was serving from stale cached data from before the violation was introduced. When Vendor A dropped its cache & needed fresh data, the violation surfaced.
Trigger
Vendor A silently added a new sync IP. The primary's allowlist did not include it. AXFR denied. Three hours later (SOA expire), Vendor A dropped the zone. The latent defect surfaced when the system tried to recover.
Write Blameless Action Items
Blameless: Target Systems, Not People
A blameless action item names something the system should do differently, not something a person should do differently. 'Train the operator' is blameful. 'Add an automated check that catches this before deploy' is blameless.
Good blameless action items cluster into three dimensions:
- Prevention: make the bad thing harder or impossible
- Detection: notice it sooner if it happens
- Recovery: limit the damage when it happens
Each item should name (1) the specific system change, (2) an owner team, & (3) the dimension it serves.
선이 침몰하지 않고 잠기는 탱크
해군 공학에서 차용한 것
정은 수중에서 수평벽으로 나누어진 수중 탱크를 가지고 있습니다. 하나의 탱크가 잠기면 배가 침몰하지 않습니다; 다른 하나가 실패하면 나머지 것에 영향을 주지 않습니다.
분산 시스템은 동일한 단어와 동일한 아이디어를 차용합니다.
Bulkhead 패턴: 의존성별로 자원 분리. 세 개의 하류 API에 호출하는 서비스는 세 개의 별도의 연결 풀, 세 개의 별도의 스레드 풀, 세 개의 별도의 재시도 예산을 사용합니다. 한 하류 서비스가 느리거나 실패하면 다른 두 개에 할당된 자원으로 소비되지 않습니다.
Bulkhead가 없는 경우: 한 개의 느린 의존성이 공유 스레드 풀을 소비하면 다른 의존성에 대한 호출은 스레드가 있는 대기 중; 전체 서비스가 응답하지 않습니다.
Bulkhead가 있는 경우: 한 개의 느린 의존성이 자신의 풀을 소비하면 해당 의존성에 대한 호출이 빠르게 실패하며 다른 의존성에 대한 호출은 정상적으로 계속됩니다; 실패하는 의존성에 국한된 폭풍 반경을 유지합니다.
서킷 브레이커
Circuit breaker 패턴: 하류 의존성에 대한 상태ful 래퍼로, 실패율을 추적합니다. 세 가지 상태:
- 닫힌(정상): 호출이 통과합니다. 실패가 기록됩니다.
- 개김(트립): 실패 임계값(예: 마지막 30초 동안 50% 실패)이 넘으면 브레이커를 개합니다. 호출은 의존성을 시도하지 않고 즉시 실패합니다. 호출자가 무한한 작업을 허비하지 않으며, 의존성이 건강하지 않을 때 로드를 받지 않게 됩니다.
- 반개(테스트): 재시동 기간이 지난 후, 브레이커는 일부 호출을 허용합니다. 성공하면 정상으로 다시 닫습니다. 실패하면 다른 재시동 기간을 위해 다시 개합니다.
중요한 인사이트: 서킷 브레이커는 알려진 건강하지 않은 기간 동안 무한한 노력을 방지하고, 하류에 복구할 기회를 제공합니다.
Bulkhead는 폭풍 반경을 제한하며, 서킷 브레이커는 폭풍이 지속되는 것을 방지합니다.
폭풍 반경을 제한하라
API 서비스는 사용자 서비스, 추천 서비스, 알림 서비스, 및 세 번째 파티 Payment API를 호출합니다. 팀은 '추천 서비스가 약간 불안정했다'는 것을 들었고, 실패할 때 시스템이 건강하게 유지하기 위해 이를 확인하고 싶습니다.
오늘의 서비스는 200개의 스레드와 단일 공유 HTTP 연결 풀을 사용하고 있습니다. 모든 네 가지 하위 시스템은 이러한 자원을 경쟁합니다. 회로 차단기가 없습니다.
실패 모드 검토를 디자인하십시오
통합
실패 모드를 검사하여 SPOFs를 식별하고, 확산 실패 패턴을 인식하고, 후송mortem을 읽을 때 트리거와 잠재적 결함을 분리할 수 있으며, 예방 / 감지 / 복구를 통해 비난하지 않는 액션 아이템을 작성할 수 있으며, Bulkheads + 회로 차단기 + 부드러운 감소와 함께 폭발 범위를 제한할 수 있습니다.
모두 다 적용하십시오.
새로운 서비스 search.example.com을 시작하고, 기본 검색 인덱스 (index.example.com), 분석 서비스 (analytics.example.com), 및 추천 서비스 (recs.example.com)를 의존하고 있습니다. 팀은 런치 전에 '실패 모드 검토'를 이끌어 내시고 싶습니다.
이 과정의 다음 단계
이 과정의 다음 단계
SPOF를 식별할 수 있고, 확산을 인식할 수 있으며, 후송mortem을 효과적으로 읽을 수 있으며, 비난하지 않는 액션 아이템을 작성할 수 있으며, 설계로 폭발 범위를 제한할 수 있습니다.
이 과정의 마지막 수업 (cs_distsys_observability_and_capacity)에서는 사용자가 문제를 알아챌 때까지 문제가 발생했는지 알아내는 데 무엇을 측정해야 하는지 가르칩니다. 시스템의 건강을 점검하는 방법, 버전 엔드포인트, 프록시 계층의 네 가지 금빛 신호, 그리고 관측 데이터와 연관된 급격한 용량 결정 방법을 설명합니다.
동료 수업: geometry_of_failure_modes_and_blast_radius에서는 그래프 노드가 병목이 되는지 여부를 결정하는 사이의 중심성(betweenness centrality)과 폭발 반경의 상한을 결정하는 최소 컷(min-cut)을 도출합니다.
잘 했어요. 앞으로 나아가요.