쿠키런: 킹덤 AWS AZ 장애 아웃라인

박새미, 이창원

이 글과 관련된 다른 글 보기


안녕하세요, 데브시스터즈 진저랩 플랫폼셀에서 소프트웨어 엔지니어로 일하고 있는 박새미, 인프라셀에서 일하고 있는 데브옵스 엔지니어 이창원입니다. 이 글에서는 2022년 NDC에서 함께 발표한 쿠키런: 킹덤, 총 56시간의 긴급 점검 회고 - 그때 그 명검은 왜 뽑아야 했는가 세션에서도 다룬 바 있는, 2021년 2월 19일에 발생한 AWS 도쿄 리전 데이터센터 장애에 대해 공유해 드리고자 합니다. 만약 정보를 접할 때 글보다 영상을 선호하는 편이시라면, 해당 세션 영상을 보시는 것을 권해드립니다.

CockroachDB? 분산 데이터베이스!

어느 평화로운 봄날, 회식 자리에서 DB 노드 한 대가 내려갔다는 에러 알람이 슬랙에 나타났지만, 데브옵스 엔지니어들은 크게 당황하지 않았습니다. AWS 같은 거대한 클라우드 벤더사라고 해서 100% SLA를 보장하지는 못하기도 하고요. 무엇보다도 CockroachDB는 이러한 장애를 견딜 수 있게 설계된 분산 데이터베이스이기 때문입니다.

기본적으로 분산 DB는 일부 노드가 정상적으로 동작하지 않아도 견딜 수 있게끔 설계됩니다. CockroachDB는 기본적으로 3대의 레플리카를 두게 되어 있으며, 각 Range[1] 는 레플리카들에 복사되어 저장됩니다. CockroachDB는 Raft 알고리즘을 채택하였고 이에 따라 DB가 N대 있다고 했을 때 (N/2) + 1 이상의 값, 즉 과반수를 정족수로 사용합니다. 결과적으로 정족수가 깨지지 않는 한 일부 노드가 죽어도 영향을 받지 않게 설계된 것이지요. 예를 들어 Replication을 5대로 설정한다면, (5/2)-1대 미만의 노드가 죽어도 DB를 이용하는 데는 문제가 없습니다. CockroachDB에 더 자세히 알고싶으시다면 진저랩 인프라셀 이준성 님이 쓰신 CockroachDB in Production을 읽어보시는 것을 추천드립니다.

다만 분산 DB라 하더라도 3분 만에 또 2대, 6분 뒤에 2대, 20분 후에 2대… 22분 만에 총 60대[2] 중 6대의 노드가 작동 불능이 되는 것은 조금 다른 이야기입니다. 엔지니어들은 빠르게 상황 파악 및 대응에 나섰습니다.

다시는 보고 싶지 않은 그 날의 Ops 채널 알람을 모사해보았습니다.

무슨 일이 일어나고 있나요?

애플리케이션 레벨에서 DB 호출과 관련된 치명적인 로직이 없고 DB 설계 자체에도 결함이 없다면, 12분만에 6대의 노드가 장애를 겪는 건 뭔가 다른 곳에 원인이 있다는 뜻일 것입니다. 이렇게 다발적이고 급격하게 Failure가 발생할 경우에는 문제의 Scope를 정확히 파악하는 것이 중요하다고 생각합니다. 여러 커뮤니티 사이트들을 살펴보니 저희만 겪는 문제가 아닌 것도 같았습니다. 또한 AWS 고객사로 알려진 다른 서비스들도 문제가 발생했다는 정황을 다양한 경로에서 파악할 수 있었습니다. 상황을 파악해본 결과, 해당 장애의 원인은 AWS 도쿄리전 데이터센터의 냉각 시스템 고장이었습니다. 서버 머신들이 과열되어 작동에 문제가 발생한 것이었습니다. 안타까운 점은 저희의 경우 고장 난 냉각 시스템의 범위에 있던 서버들이 게임 서버가 아니라 하필 DB였고, AZ 장애에 대한 고려가 배제되었었다는 점입니다. 게임 서버였다면 큰 문제 없이 자동으로 새 서버 인스턴스가 떴을 것이고, 순간적인 서비스 불안정 정도는 발생했을 수 있었겠지만, 점검을 걸 필요까지는 없었을 것입니다.

AZ 장애는 왜 고려하지 않았는가?

CockroachDB는 AZ 혹은 Region 장애까지도 대비할 수 있는 Locality라는 기능이 있습니다. 하지만 자동으로 지정되는 것이 아니고, 당연하게도 (사람) Operator가 알맞은 Topology 설정을 따로 해줘야 합니다. 그러면 노드가 클러스터에 합류한 이후에 Replica를 분배받을 때, 하나의 Topology가 완전히 날아가더라도 데이터 유실이 발생하지 않도록 분배받게 됩니다.

02 crdb locality topology
Region 장애에서도 올바르게 설정된 Follow-the-Workload Topology 덕분에 데이터 유실을 방지하는 시나리오

저희는 Kubernetes 위에 CockroachDB를 배포하였고, 데이터베이스라는 하나의 논리적인 단위이니만큼 하나의 통일된 Helm Chart를 사용해서 배포를 관리하고자 했습니다. Multi-AZ 배포를 위한 Topology는 CockroachDB Pod이 생성되는 시점에 배정이 되어야 하는데, 이 정보는 배포를 준비하는 차트 작성 단계에는 알 수가 없고, Pod이 특정 노드에 물리적으로 배정이 되고 난 이후에야 비로소 정해지며 Kubernetes API를 통해서도 제공이 되지 않습니다. 작성에 필요한 정보가 부재한 상황을 우회하려면 기술적 복잡성이 과도하게 가중되는 상황이었기 때문에, 자연스레 이 정도의 중요도가 있는 작업인지를 따져보게 되었습니다. 데브시스터즈는 2013년 쿠키런 for Kakao 시절부터 AWS를 사용해왔지만, AZ 장애가 한번도 발생하지 않았다는 점을 고려했을때, 차라리 보다 시급한 조치가 필요한 다른 일부터 진행하기로 결정했습니다. 대표적으로, Instance Retirement 같이 보다 흔하고 동시다발적으로 발생할 수 있는 시나리오에 대비하기로 하였습니다. 이전 기록들을 기반으로 동시에 여러 노드가 Retire될 확률을 경험적으로 계산하여 3대까지는 동시에 내려가도 데이터 손실이 발생하지 않도록 Replication Factor를 7로 설정해두었습니다. 물론 이 결정은 오랜 기간 안정적으로 사용해왔던 클라우드 프로바이더에 대한 신뢰가 있었기에 할 수 있었던 결정이었습니다. 으레 플래그들이 그렇듯이, 그때는 이것이 아주 완벽한 플래그로 작용하게 될 결정이라는 것을 몰랐습니다…

장애 영향 범위

결과적으로 안전 마진으로 설정해둔 3대를 넘어서는 6대의 노드에서 Host Status Failure가 발생했기 때문에 전체 25,000개의 Range 중 Data Range 2개, System Range 32개가 소실되었습니다. Range의 레플리카는 7로 설정되어 있었고, 각 Range의 레플리카 중 과반인 4개 이상의 Replica가 하필 문제의 노드 6대에 들어있을 확률을 계산해보면 대략 0.09%입니다. Range 각각에 대해 독립사건이라고 가정했을 때, 동전을 25000번 던지는 것과 유사하니 B(25000, 0.09)라고 할 수 있습니다. 평균적으로는 약 23개가 영향을 받는 정도의 확률이고, 32개의 Range가 소실될 확률까지 계산해보면 약 2.3% 정도에 불과합니다.

03 if only this were my gacha
32개의 Range에 대해서 완전 유실이 발생했을 확률을 계산해본 정규분포 그래프

System Range의 경우 클러스터에 저장된 데이터들의 위치를 포함한 각종 메타 정보가 포함되어 있습니다. Cockroach Labs 서포트 엔지니어 측에 확인해보니 System Range는 소실되어도 복구할 수 있다고 하여, 저희는 실제 유저분들의 데이터가 저장된 Data Range 2개를 복구하는 데 최대한 집중하였습니다.

우리는 해결할 것이다 늘 그랬듯이

지난번 장애에서 세 가지 해결 방법을 동시에 진행했듯이, 이번에는 두 가지 방법을 동시에 진행하였습니다. 두 방법은 생존한 54대 노드의 데이터와 Consistency 확인이 필요하다는 점에서는 동일했고, 54대가 들고 있지 않은 나머지 데이터를 가져오는 방법에 있어서 차이가 있었는데요. 해결 방법1은 Quorum이 깨진 레플리카에 남아있는 데이터들을 긁어오는 것이고, 해결 방법2는 백업에서 퍼오는 것이었습니다. 일단 공통적으로 필요한 작업인 54대의 생존한 노드로부터 테이블 백업 기능을 이용하여 데이터를 꺼내려고 시도했지만, 잘되지 않았습니다. 깨진 Range가 있어서 잘 안된 게 아닐까 추측할 수 있었습니다. 따라서 일종의 우회전략을 사용했는데요. INSERT INTO new_table SELECT * FROM old_table WHERE {unaffected_ranges} 같은 식으로 깨진 Range를 제외한 데이터를 새 테이블에 이사시켰습니다. 한편 백업 데이터를 활용하는 해결 방법2의 경우에는 주기적인 백업 데이터를 활용하는데, 이 백업 데이터를 퍼오는 작업 속도가 매우 느렸습니다. 8시간이 지나도 완료가 될 기미가 보이지 않았습니다. 저희는 조금이라도 점검 시간을 줄이고 싶었고, 어떻게 하면 더 빨리 데이터를 이사시킬 수 있을지 고민하던 찰나 생각지 못한 해결 방법3이 등장하게 됩니다.

CockroachDB 소스코드를 살펴보던 중 debug unsafe-remove-dead-replicas[3]라는 명령어를 발견한 것이었습니다. Quorum이 깨진 Range를 복구 시도하는 것인데, unsafe라는 키워드에서도 짐작하실 수 있듯 일종의 흑마법(?) 같은 명령어입니다. 해당 명령어에는 긴 주석이 달려있었는데요. 주석을 거칠게 번역하자면, “이 명령은 복구를 위한 최후의 수단이다. 데이터가 만약 복구된다고 하더라도 그 데이터가 정합성이 맞다는 보장도 없다. 이 명령은 꼭 Cockroach Labs 엔지니어가 보는 앞에서만 사용해라" 정도로 번역할 수 있을 겁니다. 어쩌면 당연하게도 공식 문서에서는 찾아볼 수 없는 기능입니다. 저희는 Cockroach Labs 엔지니어들과 줌으로 화면 공유를 하면서 해당 명령어를 실행했습니다. 결과적으로 흑마법은 성공적이었으며 소실되었던 2개 Range 중 하나를 살릴 수 있었습니다.

04 cockroach debug unsafe remove dead replicas
CockroachDB 소스코드에서 찾아볼 수 있는 해당 명령어에 대한 무서운 주석

나머지 1개의 Range도 당연히 복원을 시도했습니다. Range가 완전히 소실된 것이 아니므로 강제로 특정 Replica를 원본으로 지정하고 다시 복제하는 기능이 있었으면 좋았으련만, 당시만 해도 그런 기능이 없었습니다.[4]

따라서 살아나지 못한 Range 1개의 복원을 위해 1차 장애와 동일한 방법을 사용하였습니다. 어떤 식으로 복원했는지는 저희 기술 블로그에 있는 CTO가 커리어를 걸고 비트 레벨까지 내려가서 DB를 해킹했던 이야기, 쿠키런: 킹덤 데이터베이스 스토리지 레이어 복원기 글을 보시면 자세한 내용을 확인하실 수 있습니다. 참고로 해당 Range에 영향받을 수 있는 유저 수는 추측하건대 적으면 500명, 많으면 2천 명 정도로 예상됩니다. 결과적으로 데이터 유실 없이 온전하게 유저 데이터를 복구할 수 있었습니다.

데브시스터즈가 얻은 교훈

AWS 도쿄 리전 데이터센터 장애는 저희 데브시스터즈에 매우 중요한 교훈을 주었습니다. 쿠키런: 킹덤 정도 규모의 게임을 서비스하기 위해서는 멀티 AZ 전략은 선택이 아니라 필수라는 것이죠. 이후 저희는 Locality 설정을 통해 데이터를 여러 지역에 걸쳐 고르게 분산시켰습니다. 낮은 확률이었지만 겪고 나니 보기에 흡족한 Helm 차트를 만들어 관리하고자 했던 마음은 쏙 들어갔습니다. 배포할 AZ마다 values 파일만 다르게 사용하는 한 벌의 차트를 사용하는, 다소 반복적이지만 확실한 방법으로 Locality를 설정하였습니다. 노드를 교체할 때는 Locality를 설정한 노드를 기존 클러스터에 편입시킨 이후 모든 AZ에 안정적으로 Replica가 복제된 것을 확인하고, 이후 Locality가 설정되지 않은 기존 노드들을 퇴역시키는 방법으로 진행했습니다.

클라우드 인프라를 제공하는 AWS 측에서도 고객인 우리를 위해서 만반의 대비를 해두었을 것입니다. 그래도 세상에는 물리적으로 어쩔 수 없는 요인, 혹은 인적 요인으로 인해서라도 문제가 발생할 수 있습니다. 이번 사건으로 저희 또한 저희의 고객을 위해서, 제삼자의 어떤 무언가에 기대는 것이 아니라 저희가 할 수 있는 최대한의 방비를 해두어야한다는 것을 새삼 깨닫게 되었습니다. 그러한 준비가 없었을 때 영향을 받는 것은, 바로 소중한 우리 고객들의 경험이니까요.

글을 마치며

2022년 NDC 세션이 공개되고 나서, 몇몇 쿠키런: 킹덤 유저분들이 “서버실 에어컨 고장이라니 공포영화가 따로 없다"고 평해주신 것이 아직도 기억에 남습니다. 공포영화와 현실의 다른 점이라면 영화는 각본대로 흘러가지만, 현실에서는 온갖 좋지 못한 것들을 막기 위해 최대한 대비를 할 수 있다는 점이겠지요. 개인적으로 AWS 도쿄 리전 AZ 장애는 “대비”라는 단어가 어디까지 무게감을 가질 수 있는지 다시금 생각하게 해주었습니다. 긴 글 읽어주셔서 감사드립니다.


[1]: CockroachDB 내부적으로 사용하는 데이터 저장 단위입니다.

[2]: 36시간 점검 이후에 DB 용량을 추가적으로 확보하기 위해 총 60대 규모의 클러스터로 확장했었습니다.

[3]: 이 명령어는 지금은 deprecate 되고 debug recover 명령어로 대체되었습니다.

[4]: 새로 추가된 recover 명령어가 이러한 기능을 지원하기 위해서 개발되었다고 합니다. 잘 동작하는지 저희가 확인해보진 않았고, 앞으로도 그냥 모르고 살았으면 좋겠습니다 😅

데브시스터즈는 최고의 인재를 찾고 있습니다.

데브시스터즈에서는 능력있는 SRE/DevOps Engineer를 찾고 있습니다.
자세한 내용은 채용 사이트를 확인해주세요!
DevOpsInfra

© 2024 Devsisters Corp. All Rights Reserved.