Roblox의 작년 73시간 장애 포스트모템
(blog.roblox.com)# 장애 요약
* 장애는 72시간 동안 발생했음
* 근본 원인은 2가지
* 비정상적으로 높은 읽기/쓰기 부하가 있는 상황에서 Consul의 새로운 스트리밍 기능을 활성화하면서 과도한 경합과 성능 저하 발생
* 특정 부하 상황에서 Consul이 리더 선출과 데이터 리플리케이션을 위한 write-ahead-log를 관리하는데 사용하는 오픈소스 BoltDB에서 성능 문제를 발생시킴
* 단일 Consul 클러스터가 이러한 이슈의 영향을 악화시킴
* Consul 구현에 감춰진 이 두가지 관련없어 보이는 이슈를 찾아내느라고 장애가 연장됨
* 장애의 원인에 대한 더 나은 가시성을 제공해야 했던 모니터링 시스템이 Consul처럼 영향받는 시스템에 의존했기 때문에 더 찾기 어려웠음
# 클러스터 환경과 HashiStack
* Roblox는 18,000 서버와 170,000 컨테이너를 운영 중
* 보통 HashiStack이라고 부르는 Nomad, Consul, Vault를 사용 중
당시 Roblox에서 스트리밍 기능을 사용하려고 Consul을 1.9에서 1.10로 업그레이드함.
# 최초 탐지(10/28 13:37)
10월 18일 오후 Vault의 성능이 떨어지고 한 Consul 서버의 CPU 부하 높아짐.
# 초기 분류(10/28 13:37 – 10/29 02:00)
* Consul 클러스터 메트릭에서 쓰기 대기 시간이 증가함
* 하드웨어 성능 저하를 원인으로 의심하고 Consul 클러스터 노드 중 하나는 교체하기 시작
* HashiCorp 직원이 합류하려 같이 작업하기 시작
* 하드웨어 교체후에도 Consul 성능은 계속 떨어지고 16:35에 플레이어 수가 평소의 50%로 떨어짐
* Consul이 서비스 디스커버리로 쓰이고 Nomad와 Vault도 Consul에 의지하고 있으므로 Consul이 SPoF였다.
* 이 때 트래픽이 원인이라는 새로운 가설을 세움. 높은 트래픽으로 Consul이 더이상 부하를 처리할 수 없다고 생각함
* Consul 클러스터의 모든 노드를 더 강력한 시스템으로 교체.(코어 2배 증가, 더 빠른 NVME SSD)
* Consul 마이그레이션이 거의 끝났지만 클러스터는 정상으로 돌아오지 않음
# 서비스 복구 시도 #1(10/29 02:00 – 04:00)
* 장애전 Consul 클러스터의 스냅샷으로 되돌리기로 함
* 사용자 데이터는 괜찮고 시스템 데이터는 일부 손실되겠지만 괜찮다고 판단
* 스냅샷 복구후 Consul과 계속 통신하는 시스템으로 인한 부하로 복구후 문제가 생길까봐 iptables로 접근을 차단함
* 스냅샷 복구후 지표가 좋아보였으나 iptables 차단을 풀자 다시 원래의 장애 상태로 돌아감
# 서비스 복구 시도 #2(10/29 04:00 – 10/30 02:00)
* 외부 트래픽을 차단하고 필수가 아닌 사용은 제거해서 수백개의 인스턴스로 운영되던 서비스가 한자리 수로 줄어듦
* 다시 서비스 복구를 시도했으나 Consul은 다시 비정상 상태가 됨
* 처음 생각했던 성능 저하 요인외에도 다른게 있다는 걸 깨닫고 Roblox 관점에서의 Consul이 아니라 Consul 내부를 보기 시작함
# 경합 분석 (10/30 02:00 – 10/30 12:00)
* 10시간 동안 더 분석한 결과 Consul 쓰기가 장시간 차단되었음을 알게됨
* 경합의 원인은 몰랐지만 초기 CPU를 64코어에서 128코어로 변경한게 경합을 더 악화시켰다고 판단
* 64 코어로 돌아가기로 결정하고 돌아갔지만 도움되지 않음
# 근본 원인 발견(10/30 12:00 – 10/30 20:00)
* Consul의 스트리밍 기능은 몇달 전부터 활성화했고 CPU 사용량과 네트워크 대역폭을 낮추었기 때문에 점진적으로 도입하던 중이었다.
* 장애 하루전 27일 14:00에 트래픽 라우팅 백엔드에서 이 기능을 활성화함.
* 하루전에 활성화 후 잘 동작했기 때문에 원인으로 생각하지 못함
* 성능 분석 후 스트리킹 코드가 높은 CPU를 유발한다는 증거를 확인
* 스트리밍을 비활성하고 배포 완료 후 Consul의 KV 쓰기 지연시간이 줄어듦을 확인(드디어!)
* HashiCorp에서는 스트리밍이 더 효율적이지만 구현시 긴 폴링 보다는 적은 수의 동시성 제어 요소(Go 채널)를 사용했다고 함 -> 높은 로드에서 단일 Go 채널의 경합을 악화시켜서 효율성을 떨어뜨림
* 돌파구는 보였지만 아직 간헐적인 리더 선출이 발견되었고 일부 리더는 이전과 비슷한 지연시간 문제가 나타남
* 특정 리더만 선출되지 않으면 클러스터는 정상이라는 판단하에 서비스를 정상상태로 돌리는데 집중
* 이후 HashiCorp는 근본 원인을 계속 조사하여 일부 리더가 느린 문제가 BoltDB 때문임을 찾아냄
# 캐시 서비스 복구(10/30 20:00 – 10/31 05:00)
* 장애 54시간 만에 서비스 복구할 준비가 됨
* 장애 중 디비는 괜찮았지만 초당 10억 요청을 처리하는 캐시 시스템은 비정상적이었다.
* 이 캐시를 복구하고 정상임을 확인하자 장애 후 61시간이 되었다.
# 사용자 복귀(10/31 05:00 – 10/31 16:00)
* 31일 5시에 서비스 복귀 준비를 시작해서 10시에 마무리함.
* DNS로 접근하는 플레이어 수를 관리해서 모니터링하면서 늘려감
* 73시간만에 모든 플레이어가 접근할 수 있게됨.
# 추가 분석과 장애로 인한 변경사항
* HashiCorp와 Roblox는 "압축" 프로세스를 개발하여 성능 문제를 해결함
* 텔레메트리 개선: 텔레메트리 시스템과 Consul 사이에 순환 읜존성이 있어서 Consul에 문제 있을때 데이터가 부족했다. 텔레메트리 시스템이 모니터링하는 시스템에 의존하지 않도록 순환 의존성 제거
* 가용영역과 데이터센터 확장
* 다른 스토리지가 있음에도 Consul에 저장하거나 불필요한 KV 데이터를 정리.
* BoltDB의 후속인 bbolt로 교체한 새 버전의 Consul을 테스트 중
* 부트스트랩 프로세스로 인해 복구가 늦어졌으므로 이를 자동화하고 새로운 도구와 프로세스를 개발 중