AZ 격리는 버텼다. 당신의 아키텍처는 아니었다.
냉각 장애로 us-east-1 한 존의 랙이 죽고 쿼럼이 함께 무너졌다. blast radius 약속이 보장하는 범위와 이를 위한 read-only 디텍터.

5월 7일 저녁, us-east-1의 한 가용 영역, 그 안의 한 데이터 홀에서 냉각이 멈췄다. 랙들이 전원을 잃었고, AWS의 blast radius는 문서가 약속한 그대로 작동했다. 피해는 use1-az4 안에 머물렀다. 다른 모든 존은 계속 서비스했다. 그날 밤 다운된 회사들은 자신들의 아키텍처 안에서 다운됐고, 우리는 이것을 이례적으로 정밀하게 알고 있다. Coinbase가 월요일에 사후 분석을 공개하며 자기 입으로 그렇게 말했기 때문이다. 그들의 표현으로, 트레이딩은 플랫폼의 존 경계가 제대로 작동한 사건 중에 "unavailable or degraded for roughly eight hours, with full recovery of all systems taking another twelve" 상태였다.
이 글은 Nothing Fails Alone 시리즈의 1부다. 이 시리즈는 클라우드 공급자에게서 구매하는 가용성과 그 위에 실제로 구축하는 가용성 사이의 간극을 다룬다. 각 부는 실제 사건 하나를 골라, 그것이 시험하는 아키텍처상의 주장을 추출하고, 당신의 계정에 직접 돌려볼 수 있는 작은 랩을 제공한다. 랩들은 GitHub의 동반 저장소 nothing-fails-alone에 있고, 하나같이 read-only다.
use1-az4에서 실제로 일어난 일
AWS Health Dashboard의 첫 사건 보고는 5월 7일 PDT 오후 5시 25분으로 타임스탬프가 찍혔고, 해결된 이벤트는 이후 대시보드에서 내려갔으므로 아래 문구는 당시 The Register가 인용한 그대로다. us-east-1의 use1-az4 가용 영역에서 발생한 문제. 후속 업데이트는 그 메커니즘을 분명히 밝혔다. "EC2 instances and EBS volumes hosted on impacted hardware are affected by the loss of power during the thermal event." PDT 오후 6시 47분까지 AWS는 온도를 정상 수준으로 되돌리는 작업을 하고 있다고 밝혔고, 해당 존에서 영향받은 EC2 인스턴스와 EBS 볼륨에 의존하는 다른 서비스들도 손상될 수 있다고 경고했다.
Coinbase의 사후 분석은 물리적 원인을 채워 넣는다. 단일 데이터 홀에서 여러 대의 냉각 장치가 동시에 고장 났고, 냉각 상실이 영향받은 랙들의 열 안전 셧다운을 촉발했다. 이것이 실무에서 "loss of power during the thermal event"가 의미하는 바다. 랙들은 파괴된 것이 아니라, 의도적으로 전원이 내려졌다. 냉각 없이 컴퓨트를 돌리면 컴퓨트가 파괴되기 때문이다. 그 랙들 위의 EC2 인스턴스와 EBS 볼륨은 한 건물, 한 존 안에서 함께 오프라인이 됐다.
복구는 점진적이었고, 점진적이라는 사실에 대해 정직했다. 그날 밤 PDT 오후 10시 11분, AWS는 추가 냉각 용량이 온라인 상태가 되어 일부 랙이 복구됐으며, 나머지는 "in a controlled and safe manner"로 복구할 예정이라고 보고했다. 냉각 용량은 5월 8일 PDT 오후 1시 50분, 첫 보고로부터 약 20시간 뒤에 사건 이전 수준으로 돌아왔고, 인스턴스 복구는 냉각 복원보다 뒤처졌다. AZ 이벤트를 15분짜리 깜박임으로 생각하고 있었다면 다시 보정하라. 열 이벤트는 물리 법칙이 그렇듯 시간 단위로 측정되고, 랙은 한꺼번에가 아니라 하나씩 돌아온다.
일어나지 않은 일
us-east-1의 다른 가용 영역들은 건강한 상태를 유지했다. 이것이 이 사건에서 받는 것보다 더 많은 주목을 받아야 할 부분이다. 바로 AWS가 실제로 약속한 부분이기 때문이다. 가용 영역은 독립적인 전원과 냉각을 갖춘 물리적으로 분리된 시설이고, 5월 7일 그 독립성은 실제 시설 장애로 시험받았으며 버텨냈다. 장애 도메인은 하나의 데이터 홀이었고, blast radius는 하나의 존 안에 머물렀다.
정직한 단서가 정확히 하나 있었는데, 이 시리즈 뒤에서 중요해지므로 인용할 가치가 있다. AWS는 타격받은 존에서 트래픽을 옮기고, 고객들에게 워크로드를 다른 us-east-1 존으로 이동하라고 권고한 뒤 이렇게 인정했다. "Customers may experience longer than usual provisioning times." 당연히 그럴 수 있다. 한 존이 죽으면, 그 리전의 올바르게 구축된 모든 멀티 AZ 아키텍처가 살아남은 존들에서 동시에 용량을 교체하기 시작한다. 존 경계는 버텼지만, 살아남은 존들은 공유 풀이고, 대규모 페일오버는 그 풀을 향한 썬더링 허드다. 그 단서를 기억해 두라. 이 시리즈의 3부는 당신의 복구 계획이 다른 모두의 복구 계획과 무엇을 공유하는지에 관한 것이다.
Coinbase 자신의 말
상장 기업의 사후 분석은 대개 아무것도 남지 않을 때까지 갈려 나간다. 이것은 그렇지 않으며, 그래서 전문을 읽을 가치가 있다. 두 문장이 아키텍처 이야기 전체를 짊어진다. 첫째, "Our matching engine was pinned to a single building." 둘째, 어떻게 그렇게 됐는지에 대한 설명이다. Coinbase Exchange의 매칭 엔진은 "runs as a Raft-based replicated cluster inside an AWS Cluster Placement Group". 그리고 존 이벤트를 8시간 장애로 바꾼 문장이 나온다. "There was no automated cross-zone failover."
클러스터 배치 그룹은 구조상 단일 AZ다. 이것은 AWS가 숨기는 제약이 아니라 제품 그 자체다. 클러스터 전략의 핵심은 인스턴스를 물리적으로 가까운, 같은 고대역폭 네트워크 세그먼트 위의 하드웨어에 몰아넣어 노드 간 지연을 EC2가 낼 수 있는 최저치로 만드는 것이다. 이것과 존 분리를 동시에 가질 수는 없다. 존 분리는 거리이고 거리는 지연이기 때문이다. 노드 간 지연의 마이크로초가 곧 제품인 매칭 엔진에게 클러스터 배치 그룹은 올바른 선택이며, Coinbase도 그렇게 말한다. "We make this choice deliberately."
문제는 그 안에 무엇이 들어 있었느냐다. Raft는 소수 노드의 상실을 견디기 위해 존재한다. Raft 노드 다섯 개를 클러스터 배치 그룹에 넣으면, 구성원들이 한 건물을 공유하는 합의 프로토콜을 구축한 셈이 된다. 이는 그들이 장애 도메인을 공유한다는 뜻이고, 따라서 프로토콜은 자신이 존재하는 유일한 목적을 더는 수행할 수 없다는 뜻이다. AWS가 미국 동부시간 오후 9시 29분에 Coinbase의 배치 그룹 안 EC2 인스턴스들을 종료했을 때, 매칭 엔진 노드 다섯 개 중 셋이 함께 다운됐고 쿼럼은 사라졌다. 데이터 홀을 공유하는 쿼럼은 쿼럼이 아니다. 그것은 이름이 다섯 개인 하나의 장애다.
자동화된 크로스 존 페일오버가 뒤를 받치지 않는 상태에서 복구는 사람의 일이 됐다. 다섯 개의 클러스터 노드가 모두 해석 가능하다는 시작 시점의 가정을 제거하기 위해, 사건 중에 배포한 긴급 코드 변경이 필요했다. 매칭 엔진은 5월 8일 미국 동부시간 오전 2시 25분에 취소 전용 모드로 돌아왔고, 전체 트레이딩은 오전 3시 49분에 재개됐으며, 리테일 사이트와 모바일 앱은 오전 9시 53분에 완전히 복구됐고, 이벤트 스트리밍 토픽의 백로그는 오후 2시에 해소됐다. 약 8시간 다운, 완전 복구까지 추가 12시간, AWS가 하나의 존 안에 가둔 사건 중에.
멀티 AZ는 구매하는 것이자 구축하는 것이다
이 사건의 일반적 형태는 Coinbase만의 이야기가 아니다. 그것은 멀티 AZ 인프라를 구매하는 것과 멀티 AZ 시스템을 구축하는 것 사이의 간극이며, 그 간극에는 반복되는 형태가 몇 가지 있다. 다음 각각은 아키텍처 다이어그램에서는 이중화된 것처럼 보이지만 실제로는 그렇지 않은 구성이다.
- 단일 서브넷 Auto Scaling 그룹. ASG는 실패한 인스턴스를 영원히 기꺼이 교체하는데, 당신이 준 서브넷 목록이 하나의 AZ로 해석되므로 계속 같은 존으로 교체한다. 그 존이 저하되면 사람이 그룹을 편집하기 전까지 원하는 용량을 채울 수 없다. 장애 속으로 치유하는 자가 치유는 자가 치유가 아니다.
- Multi-AZ 없는 RDS. 동기 스탠바이가 없다는 것은 존 상실이 페일오버가 아니라 복원이라는 뜻이다. 스냅샷으로부터, 시간 단위로, 마지막 백업 시점까지의 데이터 손실을 안고. 그 옆의 DR 계획은 대개 1분 미만 페일오버를 가정하는데, 누군가 Multi-AZ 문서를 읽었고 아무도 콘솔의 플래그를 확인하지 않았기 때문이다.
- 클러스터 배치 그룹 안의 쿼럼. Coinbase 패턴이다. Raft, etcd, 또는 ZooKeeper의 노드 셋, 다섯, 또는 일곱 개가 정의상 단일 AZ인 그룹 안에 몰려 있는 것. 합의 계층은 건물이 이견을 내밀기 직전까지 자신을 건강하고 내결함성이 있다고 보고한다.
- 크로스 AZ NAT 의존성. 존 B의 프라이빗 서브넷의 기본 경로가 존 A의 NAT 게이트웨이를 가리키는 경우. 존 A의 장애는 자기 존이 완벽하게 건강한 인스턴스들의 아웃바운드 트래픽을 앗아간다. 이것은 화살표가 존이 아니라 "NAT"를 가리키기 때문에 모든 다이어그램에서 보이지 않는다.
이 중 어느 것도 이국적이지 않다. 이들 모두는 부하 테스트, 인스턴스만 죽이는 게임 데이, 그리고 대부분의 아키텍처 리뷰를 통과한다. 각각은 존 전체가 죽을 때에만 실패하는데, 존 전체가 실패하는 일은 충분히 드물어서 그 가정이 수년간 시험받지 않은 채 남기 때문이다.
랩: 단일 AZ 집중 디텍터
주장은 검증 가능해야 하므로, 이 부에는 랩이 딸려 있다. 01-single-az-detector는 위 패턴들과 세 가지를 더 찾아 현재 계정과 리전을 스캔하는 단일 파이썬 파일이다. 엄밀한 의미에서 read-only다. 그것이 하는 모든 API 호출은 Describe*다. 일곱 가지를 점검한다. 단일 AZ Auto Scaling 그룹, 클러스터 배치 그룹, 배치 그룹 안의 쿼럼 크기 클러스터, 단일 AZ RDS 인스턴스, 구성원이 모두 한 존에 있는 RDS 클러스터, Multi-AZ 없는 ElastiCache 복제 그룹, 그리고 기본 경로가 NAT 게이트웨이에 닿기 위해 존을 넘는 프라이빗 서브넷.
$ python3 detect.py
SEVERITY | RESOURCE | AZ | WHY
--------------------------------------------------------------------------...
CRITICAL | placement-group/matching-prod | us-east-1a (use1-az4) | 3 instances in cluster placement group 'matching-prod': a quorum-sized cluster in a group that is single-AZ by construction. ...
HIGH | asg/api-workers | us-east-1c (use1-az2) | Auto Scaling group spans exactly one AZ. Every replacement instance lands in the same zone ...
HIGH | rds-cluster/orders | us-east-1a (use1-az4) | All members of DB cluster 'orders' sit in one AZ. Aurora failover needs a reader in a different zone ...
MEDIUM | subnet-0a1b2c3d -> nat-0e4f5a6b | us-east-1b (use1-az6) -> us-east-1a (use1-az4) | Private subnet in us-east-1b (use1-az6) routes 0.0.0.0/0 through a NAT gateway in us-east-1a (use1-az4) ...
4 finding(s).CRITICAL 심각도를 받는 점검은 QUORUM_IN_ONE_AZ다. 정확히 3, 5, 또는 7개의 실행 중 인스턴스를 담은 클러스터 배치 그룹이다. 그 개수는 임의가 아니다. Raft, etcd, 또는 ZooKeeper 쿼럼의 형태이며, 한 존에 집중된 쿼럼 모양의 워크로드는 Coinbase를 다운시킨 바로 그 구성이다. 다른 인스턴스 개수를 가진 배치 그룹도 여전히 플래그되지만, HIGH로 표시된다. 누군가 의도적으로 선택했는지 확인해야 할 지연 최적화이기 때문이다.
두 가지 동작은 의도적이다. 첫째, 종료 코드다. 스캔은 발견 항목이 있든 없든 완료되면 언제나 0으로 종료한다. 이것은 게이트가 아니라 리포트이기 때문이다. IAM이 API 중 하나를 거부하면 그 점검은 stderr에 한 줄 경고를 남기고 건너뛰며, 나머지 점검은 계속 실행되고, 종료 코드는 0으로 유지된다. 스캔이 아예 실행될 수 없을 때에만 종료 코드 2를 받는다. 자격 증명 없음, 리전 없음, boto3 없음. 깔끔한 표를 믿기 전에 stderr를 확인하라. 둘째, 모든 발견 항목은 존을 이름과 ID 둘 다로 보고한다. 예를 들어 us-east-1a (use1-az4). 이름은 AWS가 계정마다 무작위화하는 별칭이고, ID는 물리적 존이다. Health Dashboard는 use1-az4라고 말했는데, ID가 없으면 그것이 당신의 us-east-1a였는지 알 수 없다. 그 구분은 오늘은 사소해 보인다. 2부는 그것이 사소하기를 멈추는 사건에 관한 것이다.
실패 모드와 주시할 것
이 모든 것 아래의 트레이드오프는 정당하므로, 없는 척하지 말고 이름을 붙여라. 단일 AZ 배치는 지연을 사고, 크로스 AZ 데이터 전송은 돈이 들며, 존 간 동기 복제는 쓰기 지연을 부담한다. Coinbase는 배치 그룹에 우연히 발을 들인 것이 아니다. 그들은 그것을 향해 설계했고 리스크를 받아들였다. 실패는 그 선택이 아니라, 그 선택의 결과, 즉 존 밖으로 나가는 자동화된 경로가 없다는 사실이 설계 리뷰에서 사전에 결정되지 않고 사건 중 오후 9시 29분에 발견됐다는 것이었다.
디텍터를 돌리는 것을 넘어 당신의 계정에서 주시할 것. 쿼럼 위치만이 아니라 쿼럼 산술이다. 한 존 안의 etcd 노드 셋 중 둘은 더 친절한 표의 한 줄로 나타나는 같은 결함이기 때문이다. 모두 같은 존으로 해석되는 여러 서브넷을 나열하는 ASG. 그래서 디텍터는 AZ 목록을 믿는 대신 VPCZoneIdentifier 서브넷을 해석한다. 복제본 없는 캐시. 존 상실은 ElastiCache를 차갑게 만들고, 그 결과의 미스 폭풍은 최악의 순간에 당신의 데이터베이스에 떨어진다. 그리고 실제 존 이벤트 중에 용량을 프로비저닝하는 것. 이것은 디텍터가 볼 수 없는 유일한 것인데, 당신의 아키텍처가 아니라 다른 모두의 아키텍처의 속성이기 때문이다. AWS가 사건 한복판에서 직접 그렇게 말했다. longer than usual provisioning times. 존 경계는 실재하고, 버텼다. 그 위에 무엇을 구축하는지는 당신의 몫이다.
다음으로 읽을 글
- 이 시리즈의 2부: 두 개의 존, 열여덟 시간, 하나의 원인, AZ 이름 대 AZ ID의 구분이 사소하기를 멈추는 곳.
- ercan.ai의 멀티 테넌트 LLM 앱: 공유 모델에서 고객 격리하기, 한 계층 위의 같은 교훈. 플랫폼이 경계를 주지만, 격리는 여전히 당신의 일이다.
- 동반 저장소: nothing-fails-alone, 이 시리즈의 모든 랩, 설계상 read-only.
AWS, 클라우드 아키텍처, 복원력 리뷰, 플랫폼 작업에 대한 컨설팅은 ercanermis.com에서 시작하라.
참고 자료
Ercan의 다른 글
같은 저자, 다른 영역의 사이트 두 개.