Roblox 서비스 재개

10월 28일부터 시작되어 10월 31일에 완전히 해결된 이번 사태로, 로블록스는 73시간 동안 서비스 중단을 겪었습니다.¹ 매일 5천만 명의 플레이어가 로블록스를 이용하고 있으며, 플레이어들이 기대하는 경험을 제공하기 위해 수백 개의 내부 온라인 서비스가 운영되고 있습니다. 다른 대규모 서비스와 마찬가지로 저희도 때때로 서비스 중단이 발생하지만, 이번 중단 기간이 유난히 길어 특히 주목할 만한 사안입니다. 이번 서비스 중단으로 인해 커뮤니티 여러분께 진심으로 사과드립니다.
이번 기술적 세부 사항을 공유하는 것은 커뮤니티 여러분께 문제의 근본 원인, 해결 과정, 그리고 향후 유사한 문제가 발생하지 않도록 우리가 취하고 있는 조치에 대해 이해를 돕기 위함입니다. 또한 이번 사고 동안 사용자 데이터가 유실되거나 무단으로 정보에 접근한 사례는 전혀 없었다는 점을 다시 한번 강조드립니다.
Roblox 엔지니어링 팀과 HashiCorp의 기술 담당자들은 힘을 합쳐 Roblox 서비스를 복구했습니다. 놀라운 자원을 투입하고 문제가 해결될 때까지 지칠 줄 모르고 저희와 협력해 준 HashiCorp 팀에 깊은 감사를 표합니다.
서비스 중단 요약
이번 서비스 중단은 지속 시간과 복잡성 면에서 유례가 없는 사건이었습니다. 팀은 근본 원인을 파악하고 서비스를 복구하기 위해 여러 가지 과제를 순차적으로 해결해야 했습니다.
- 서비스 중단은 73시간 동안 지속되었습니다.
- 근본 원인은 두 가지 문제 때문이었습니다. 비정상적으로 높은 읽기 및 쓰기 부하 상태에서 Consul의 비교적 새로운 스트리밍 기능을 활성화한 결과, 과도한 경합이 발생하고 성능이 저하되었습니다. 또한, 당사의 특정 부하 조건으로 인해 BoltDB에서 병리적인 성능 문제가 발생했습니다. 오픈 소스 BoltDB 시스템은 Consul 내에서 리더 선출 및 데이터 복제를 위한 사전 기록 로그(WAL)를 관리하는 데 사용됩니다.
- 여러 워크로드를 지원하는 단일 Consul 클러스터는 이러한 문제의 영향을 더욱 악화시켰습니다.
- Consul 구현 깊숙이 자리 잡은, 서로 별개의 이 두 가지 문제를 진단하는 데 따르는 어려움이 장시간의 서비스 중단에 주된 원인이었습니다.
- 정전 원인을 더 명확하게 파악할 수 있게 해줄 중요한 모니터링 시스템들은 Consul과 같이 영향을 받은 시스템들에 의존하고 있었습니다. 이러한 조합은 문제 분류 과정을 심각하게 방해했습니다.
- 우리는 Roblox를 장기간의 완전 중단 상태에서 복구하는 데 신중하고 세심한 접근 방식을 취했으며, 이 과정에도 상당한 시간이 소요되었습니다.
- 모니터링 개선, 가시성 스택의 순환 의존성 제거, 부트스트랩 프로세스 가속화를 위한 엔지니어링 노력을 가속화했습니다.
- 여러 가용 영역 및 데이터 센터로 이전하기 위해 노력하고 있습니다.
- 이번 사건의 근본 원인이었던 Consul의 문제들을 해결하고 있습니다.
서문: 당사의 클러스터 환경 및 HashiStack
Roblox의 핵심 인프라는 Roblox 데이터 센터에서 운영됩니다. 당사는 자체 하드웨어를 배포 및 관리하고, 그 하드웨어 위에 자체 컴퓨팅, 스토리지 및 네트워킹 시스템을 구축합니다. 당사의 배포 규모는 18,000대 이상의 서버와 170,000개의 컨테이너로 매우 방대합니다.
여러 사이트에 걸쳐 수천 대의 서버를 운영하기 위해, 우리는 일반적으로 “HashiStack”으로 알려진 기술 제품군을 활용합니다. Nomad, Consul, Vault는 전 세계의 서버와 서비스를 관리하고, Roblox 서비스를 지원하는 컨테이너를 오케스트레이션하는 데 사용하는 기술입니다.
Nomad는 작업 스케줄링에 사용됩니다. 어떤 컨테이너가 어떤 노드에서 실행될지, 그리고 어떤 포트를 통해 접근 가능한지를 결정합니다. 또한 컨테이너의 상태를 검증합니다. 이 모든 데이터는 IP:포트 조합으로 구성된 데이터베이스인 서비스 레지스트리(Service Registry)로 전달됩니다. Roblox 서비스는 서로 통신하기 위해 서비스 레지스트리를 사용하여 서로를 찾습니다. 이 과정을 “서비스 디스커버리(service discovery)”라고 합니다. 우리는 서비스 디스커버리, 상태 점검, 세션 잠금(상위 구축된 HA 시스템용), 그리고 KV 스토어로서 Consul을 사용합니다.
Consul은 두 가지 역할을 수행하는 머신 클러스터 형태로 배포됩니다. “Voters”(5대)는 클러스터의 상태를 공식적으로 관리하며, “Non-voters”(추가 5대)는 읽기 요청을 확장하는 데 도움을 주는 읽기 전용 복제본입니다. 언제든지 클러스터는 Voters 중 하나를 리더로 선출합니다. 리더는 다른 Voters에게 데이터를 복제하고, 기록된 데이터가 완전히 커밋되었는지 확인하는 역할을 담당합니다. Consul은 리더 선출과 클러스터 내 상태 분배를 위해 Raft라는 알고리즘을 사용하며, 이를 통해 클러스터의 각 노드가 업데이트에 대해 합의하도록 보장합니다. 하루 동안 리더 선출을 통해 리더가 여러 번 바뀌는 것은 드문 일이 아닙니다.
다음은 사고 발생 후 Roblox의 Consul 대시보드에서 캡처한 최근 스크린샷입니다. 이 블로그 게시물에서 언급된 주요 운영 지표 중 다수는 정상 수준을 보이고 있습니다. 예를 들어, KV 적용 시간은 300ms 미만이면 정상으로 간주되는데, 현재 이 수치는 30.6ms입니다. Consul 리더는 지난 32ms 동안 클러스터 내 다른 서버들과 통신한 바 있으며, 이는 매우 최근의 상황입니다.

10월 사건이 발생하기 몇 달 전, Roblox는 새로운 스트리밍 기능을 활용하기 위해 Consul 1.9에서 Consul 1.10으로 업그레이드했습니다. 이 스트리밍 기능은 Roblox와 같은 대규모 클러스터에 업데이트를 배포하는 데 필요한 CPU 및 네트워크 대역폭을 대폭 줄이도록 설계되었습니다.
초기 탐지 (10/28 13:37)
10월 28일 오후, Vault 성능이 저하되었고 단일 Consul 서버에서 높은 CPU 부하가 발생했습니다. Roblox 엔지니어들은 조사에 착수했습니다. 이 시점에서는 플레이어들에게 미치는 영향은 없었습니다.
초기 대응 (10/28 13:37 – 10/29 02:00)
초기 조사 결과, Vault와 다른 많은 서비스가 의존하는 Consul 클러스터의 상태가 비정상적인 것으로 나타났습니다. 구체적으로, Consul 클러스터 메트릭을 분석한 결과, Consul이 데이터를 저장하는 기반 KV 스토어의 쓰기 지연 시간이 증가한 것으로 나타났습니다. 해당 작업의 50분위수 지연 시간은 보통 300ms 미만이었으나, 현재는 2초로 늘어났습니다. Roblox 규모의 시스템에서 하드웨어 문제는 드문 일이 아니며, Consul은 하드웨어 장애를 견딜 수 있습니다. 하지만 하드웨어가 고장 난 것이 아니라 단순히 느려진 경우, 전체 Consul 성능에 영향을 미칠 수 있습니다. 이 경우, 팀은 하드웨어 성능 저하를 근본 원인으로 의심하고 Consul 클러스터 노드 중 하나를 교체하는 작업을 시작했습니다. 이것이 사고 진단을 위한 첫 번째 시도였습니다. 이 무렵, HashiCorp 직원들이 Roblox 엔지니어들과 합류하여 진단 및 문제 해결을 지원했습니다. 이 시점부터 "팀" 및 "엔지니어링 팀"에 대한 모든 언급은 Roblox와 HashiCorp 직원을 모두 지칭합니다.
새로운 하드웨어를 도입했음에도 불구하고 Consul 클러스터의 성능 저하는 계속되었습니다. 16시 35분, 온라인 플레이어 수는 평소의 50% 수준으로 떨어졌습니다.

이번 트래픽 급증은 시스템 상태의 심각한 악화와 맞물려, 결국 시스템 전체가 중단되는 결과를 초래했습니다. 그 이유는 무엇일까요? Roblox 서비스가 다른 서비스와 통신하려 할 때, 통신 대상 서비스의 위치를 파악하기 위해 최신 정보를 제공하는 Consul에 의존하기 때문입니다. 하지만 Consul의 상태가 비정상적일 경우, 서버들은 연결에 어려움을 겪습니다. 게다가 Nomad와 Vault도 Consul에 의존하고 있으므로, Consul의 상태가 비정상적일 때 시스템은 새로운 컨테이너를 스케줄링하거나 인증에 사용되는 프로덕션 시크릿을 가져올 수 없습니다. 요컨대, Consul이 단일 장애 지점이었고 그 상태가 비정상적이었기 때문에 시스템이 실패한 것입니다.
이 시점에서 팀은 문제의 원인에 대해 새로운 가설을 세웠습니다. 바로 트래픽 증가였습니다. 어쩌면 시스템이 한계점에 도달하여 Consul이 느려진 것일 수도 있고, Consul이 실행 중인 서버들이 더 이상 부하를 감당하지 못한 것은 아닐까요? 이것이 사고의 근본 원인을 진단하기 위한 두 번째 시도였습니다.
사고의 심각성을 고려하여, 팀은 Consul 클러스터의 모든 노드를 더 강력하고 새로운 서버로 교체하기로 결정했습니다. 이 새로운 서버들은 128코어(기존 대비 2배 증가)와 더 새롭고 빠른 NVMe SSD 디스크를 갖추고 있었습니다. 19:00까지 팀은 클러스터의 대부분을 새로운 서버로 마이그레이션했지만, 클러스터는 여전히 정상 상태가 아니었습니다. 클러스터는 대다수의 노드가 쓰기 작업을 감당하지 못하고 있다고 보고했으며, KV 쓰기 작업의 50분위수 지연 시간은 일반적인 300ms 이하가 아닌 여전히 약 2초 수준이었습니다.
서비스 복구 시도 #1 (10/29 02:00 – 04:00)
Consul 클러스터를 정상 상태로 복구하려는 첫 두 번의 시도는 실패했습니다. 여전히 높은 KV 쓰기 지연 시간이 관찰되었을 뿐만 아니라, 설명할 수 없는 새로운 증상도 나타났습니다. 바로 Consul 리더가 다른 투표자들과 정기적으로 동기화되지 않는 현상이었습니다.
팀은 전체 Consul 클러스터를 중단하고, 장애가 시작되기 몇 시간 전의 스냅샷을 사용하여 상태를 초기화하기로 결정했습니다. 이로 인해 소량의 시스템 구성 데이터(사용자 데이터는 아님)가 손실될 수 있다는 점을 인지하고 있었습니다. 장애의 심각성과 필요 시 수동으로 이 시스템 구성 데이터를 복원할 수 있다는 확신을 고려할 때, 이는 수용 가능한 수준이라고 판단했습니다.
시스템이 정상 상태였을 때 찍은 스냅샷으로 복원하면 클러스터가 정상 상태로 돌아올 것으로 예상했지만, 한 가지 추가적인 우려 사항이 있었습니다. 비록 이 시점에서 Roblox 시스템에는 사용자 생성 트래픽이 흐르지 않았지만, Roblox 내부 서비스들은 여전히 가동 중이었고 의존성의 위치를 파악하고 상태 정보를 업데이트하기 위해 성실히 Consul에 접속하고 있었습니다. 이러한 읽기 및 쓰기 작업은 클러스터에 상당한 부하를 발생시키고 있었습니다. 우리는 클러스터 재설정이 성공하더라도 이러한 부하로 인해 클러스터가 즉시 비정상 상태로 되돌아갈까 봐 우려했습니다. 이 문제를 해결하기 위해 클러스터의 iptables를 구성하여 액세스를 차단했습니다. 이를 통해 클러스터를 통제된 방식으로 다시 가동할 수 있었고, 사용자 트래픽과 무관하게 Consul에 가해지는 부하가 문제의 일부인지 파악하는 데 도움이 되었습니다.
리셋은 순조롭게 진행되었고, 초기에는 메트릭도 양호해 보였습니다. iptables 차단 설정을 해제하자 내부 서비스에서 발생하는 서비스 디스커버리 및 헬스 체크 부하가 예상대로 돌아왔습니다. 하지만 Consul 성능은 다시 저하되기 시작했고, 결국 우리는 시작점으로 되돌아갔습니다. KV 쓰기 작업의 50번째 백분위수 지연 시간이 다시 2초로 돌아온 것입니다. Consul에 의존하는 서비스들이 스스로를 "비정상(unhealthy)"으로 표시하기 시작했고, 결국 시스템은 이제 익숙해진 문제 상태로 되돌아갔습니다. 시각은 04:00이었습니다. 분명히 Consul에 가해지는 부하 중 무언가가 문제를 일으키고 있었지만, 사고가 발생한 지 14시간이 넘도록 우리는 그 원인을 여전히 알지 못했습니다.
서비스 복구 시도 #2 (10/29 04:00 – 10/30 02:00)
우리는 하드웨어 고장을 배제했습니다. 더 빠른 하드웨어는 도움이 되지 않았을 뿐만 아니라, 나중에 알게 된 바와 같이 안정성을 저해할 가능성도 있었습니다. Consul의 내부 상태를 초기화하는 것도 소용이 없었습니다. 사용자 트래픽은 유입되지 않았음에도 불구하고 Consul은 여전히 느렸습니다. 우리는 iptables를 활용하여 트래픽이 클러스터로 서서히 유입되도록 했습니다. 수천 개의 컨테이너가 재연결을 시도하는 엄청난 트래픽 양 때문에 클러스터가 단순히 비정상적인 상태로 되돌아가고 있었던 것일까요? 이것이 사고의 근본 원인을 진단하기 위한 세 번째 시도였습니다.
엔지니어링 팀은 Consul 사용량을 줄인 다음, 신중하고 체계적으로 다시 도입하기로 결정했습니다. 깨끗한 출발점을 확보하기 위해 남아 있던 외부 트래픽도 차단했습니다. 우리는 Consul을 사용하는 서비스의 철저한 목록을 작성하고, 필수적이지 않은 모든 사용을 비활성화하도록 구성 변경을 적용했습니다. 대상 시스템과 구성 변경 유형이 매우 다양했기 때문에 이 과정에는 몇 시간이 소요되었습니다. 일반적으로 수백 개의 인스턴스가 실행되던 Roblox 서비스는 한 자릿수 수준으로 축소되었습니다. 클러스터에 추가적인 여유 공간을 확보하기 위해 상태 확인 주기를 60초에서 10분으로 늘렸습니다. 서비스 중단이 시작된 지 24시간이 넘은 10월 29일 16:00, 팀은 Roblox를 다시 온라인으로 복구하기 위한 두 번째 시도를 시작했습니다. 이번 재시작 시도 초기 단계도 순조로워 보였으나, 10월 30일 02:00이 되자 Consul은 다시 비정상적인 상태로 돌아갔습니다. 이번에는 Consul에 의존하는 Roblox 서비스의 부하가 훨씬 적은 상황이었음에도 불구하고 말이죠.
이 시점에서, 28일에 처음 발견된 성능 저하의 원인이 단순히 Consul의 전체 사용량 때문만은 아니라는 것이 분명해졌습니다. 이러한 사실을 인지한 팀은 다시 방향을 전환했습니다. 팀은 Consul에 의존하는 Roblox 서비스의 관점에서 Consul을 바라보는 대신, 단서를 찾기 위해 Consul의 내부 구조를 살펴보기 시작했습니다.
경합 현상 조사 (10/30 02:00 – 10/30 12:00)
이후 10시간 동안 엔지니어링 팀은 디버그 로그와 운영 체제 수준의 메트릭을 심층적으로 분석했습니다. 이 데이터는 Consul KV 쓰기 작업이 장시간 차단되고 있음을 보여주었습니다. 즉, “경합(contention)”이 발생하고 있었던 것입니다. 경합의 원인은 즉시 명확하지 않았지만, 장애 초기에 64코어 서버에서 128코어 서버로 전환한 것이 문제를 악화시켰을 수 있다는 가설이 제기되었습니다. 아래 스크린샷에 표시된 htop 데이터와 성능 디버깅 데이터를 검토한 후, 팀은 장애 발생 전과 유사한 64코어 서버로 되돌리는 것이 타당하다고 결론지었습니다. 팀은 하드웨어 준비에 착수했습니다. Consul을 설치하고, 운영 체제 구성을 세 번에 걸쳐 확인하며, 가능한 한 세심하게 시스템을 서비스 운영에 대비시켰습니다. 그 후 팀은 Consul 클러스터를 다시 64코어 서버로 전환했으나, 이 변경은 문제를 해결하지 못했습니다. 이는 사고의 근본 원인을 진단하기 위한 네 번째 시도였습니다.


근본 원인 파악 (10/30 12:00 – 10/30 20:00)
몇 달 전, 저희는 일부 서비스에 새로운 Consul 스트리밍 기능을 적용했습니다. Consul 클러스터의 CPU 사용량과 네트워크 대역폭을 줄이기 위해 설계된 이 기능은 예상대로 작동했으므로, 이후 몇 달 동안 더 많은 백엔드 서비스에 이 기능을 단계적으로 적용했습니다. 서비스 중단 하루 전인 10월 27일 14:00에, 저희는 트래픽 라우팅을 담당하는 백엔드 서비스에 이 기능을 적용했습니다. 이번 적용의 일환으로, 연말에 흔히 발생하는 트래픽 증가에 대비하기 위해 트래픽 라우팅을 지원하는 노드 수를 50% 늘렸습니다. 사고 발생 전 하루 동안은 이 수준의 스트리밍 상태에서도 시스템이 잘 작동했기 때문에, 처음에는 성능이 왜 변했는지 명확하지 않았습니다. 그러나 Consul 서버의 성능 보고서(perf reports)와 플레임 그래프(flame graphs)를 분석한 결과, 높은 CPU 사용량을 유발하는 경합 현상의 원인이 스트리밍 코드 경로에 있다는 증거를 발견했습니다. 이에 트래픽 라우팅 노드를 포함한 모든 Consul 시스템에서 스트리밍 기능을 비활성화했습니다. 구성 변경 사항이 15:51에 완전히 적용되자, Consul KV 쓰기 작업의 50번째 백분위수(50th percentile)가 300ms로 낮아졌습니다. 마침내 돌파구를 찾은 것이었습니다.
스트리밍이 왜 문제가 되었을까요? HashiCorp는 스트리밍이 전반적으로 더 효율적이긴 하지만, 롱 폴링(long polling)에 비해 구현 과정에서 동시성 제어 요소(Go 채널)를 더 적게 사용한다고 설명했습니다. 매우 높은 부하, 특히 매우 높은 읽기 부하와 쓰기 부하가 동시에 발생할 경우, 스트리밍의 설계 방식은 단일 Go 채널에 가해지는 경합을 악화시켜 쓰기 작업 중 블로킹을 유발하고, 이로 인해 효율성이 현저히 떨어지게 됩니다. 이러한 현상은 코어 수가 많은 서버에서 나타나는 효과도 설명해 주었습니다. 해당 서버들은 NUMA 메모리 모델을 갖춘 듀얼 소켓 아키텍처였기 때문입니다. 따라서 이 아키텍처 하에서는 공유 리소스에 대한 추가적인 경합이 더욱 심해졌습니다. 스트리밍을 비활성화함으로써 우리는 Consul 클러스터의 상태를 획기적으로 개선했습니다.
이러한 돌파구에도 불구하고, 아직 완전히 해결된 것은 아니었습니다. Consul이 간헐적으로 새로운 클러스터 리더를 선출하는 현상은 정상적이었지만, 일부 리더에서는 스트리밍을 비활성화하기 전과 동일한 지연 시간 문제가 발생하는 것을 확인했는데, 이는 정상적인 현상이 아니었습니다. 리더 지연 문제의 근본 원인을 가리키는 명백한 단서가 없었고, 특정 서버가 리더로 선출되지 않는 한 클러스터가 정상적으로 작동한다는 증거가 있었기에, 팀은 문제가 있는 리더가 계속 선출되는 것을 막는 방식으로 문제를 우회하기로 실용적인 결정을 내렸습니다. 이를 통해 팀은 Consul에 의존하는 Roblox 서비스를 정상 상태로 복구하는 데 집중할 수 있었습니다.
하지만 느린 리더들의 문제는 무엇이었을까요? 우리는 사고 당시 이를 파악하지 못했지만, HashiCorp 엔지니어들이 서비스 중단 후 며칠 만에 근본 원인을 규명했습니다. Consul은 Raft 로그를 저장하기 위해 BoltDB라는 널리 사용되는 오픈소스 지속성 라이브러리를 사용합니다. 이 라이브러리는 Consul 내의 현재 상태를 저장하는 데 사용되는 것이 아니라, 적용 중인 작업들의 롤링 로그를 저장하는 데 사용됩니다. BoltDB가 무한정 커지는 것을 방지하기 위해 Consul은 정기적으로 스냅샷을 수행합니다. 스냅샷 작업은 Consul의 현재 상태를 디스크에 기록한 후 BoltDB에서 가장 오래된 로그 항목을 삭제합니다.
그러나 BoltDB의 설계상, 가장 오래된 로그 항목이 삭제되더라도 BoltDB가 디스크에서 차지하는 공간은 줄어들지 않습니다. 대신, 삭제된 데이터를 저장하는 데 사용되었던 모든 페이지(파일 내의 4KB 단위 세그먼트)는 “사용 가능(free)”으로 표시되어 이후 쓰기 작업에 재사용됩니다. BoltDB는 이러한 여유 페이지를 “프리리스트(freelist)”라는 구조에서 추적합니다. 일반적으로 프리리스트를 업데이트하는 데 걸리는 시간은 쓰기 지연 시간에 큰 영향을 미치지 않지만, Roblox의 워크로드는 프리리스트 유지 관리에 막대한 비용을 초래하는 BoltDB의 심각한 성능 문제를 드러냈습니다.
캐싱 서비스 복구 (10/30 20:00 – 10/31 05:00)
서비스 중단이 시작된 지 54시간이 지났습니다. 스트리밍이 비활성화되고 느린 리더가 선출된 상태로 유지되는 것을 방지하는 프로세스가 마련됨에 따라, 이제 Consul은 지속적으로 안정적인 상태를 유지하고 있었습니다. 팀은 서비스 복구에 집중할 준비가 되어 있었습니다.
Roblox는 백엔드에 일반적인 마이크로서비스 패턴을 사용합니다. 마이크로서비스 "스택"의 최하단에는 데이터베이스와 캐시가 위치합니다. 이 데이터베이스들은 장애의 영향을 받지 않았지만, 정상적인 시스템 운영 시 여러 계층에 걸쳐 초당 10억 건의 요청을 처리하는 캐싱 시스템은 비정상적인 상태였습니다. 당사의 캐시는 기본 데이터베이스에서 쉽게 다시 채울 수 있는 일시적인 데이터를 저장하므로, 캐싱 시스템을 정상 상태로 되돌리는 가장 쉬운 방법은 이를 재배포하는 것이었습니다.
캐시 재배포 과정에서 일련의 문제가 발생했습니다:
- 아마도 앞서 수행된 Consul 클러스터 스냅샷 재설정 때문으로 보이며, 캐시 시스템이 Consul KV에 저장하는 내부 스케줄링 데이터가 부정확했습니다.
- 소규모 캐시의 배포는 예상보다 오래 걸렸고, 대규모 캐시의 배포는 완료되지 않았습니다. 조사 결과, 작업 스케줄러가 비정상 상태가 아닌 완전히 정상으로 인식한 비정상 노드가 있는 것으로 밝혀졌습니다. 이로 인해 작업 스케줄러는 해당 노드에 캐시 작업을 과도하게 스케줄링하려 했으나, 노드가 비정상 상태였기 때문에 실패했습니다.
- 캐싱 시스템의 자동 배포 도구는 대규모 클러스터를 처음부터 구축하기 위한 반복적인 시도가 아니라, 이미 대규모 트래픽을 처리하고 있는 대규모 배포에 대한 점진적인 조정을 지원하도록 설계되었습니다.
팀은 밤을 새워가며 이러한 문제를 파악하고 해결하며, 캐싱 시스템이 올바르게 배포되었는지 확인하고 정상 작동을 검증했습니다. 장애 발생 61시간 만인 10월 31일 오전 5시, 우리는 정상적인 Consul 클러스터와 캐싱 시스템을 확보했습니다. 이제 나머지 Roblox 서비스를 가동할 준비가 되었습니다.
플레이어의 복귀 (10/31 05:00 – 10/31 16:00)
최종 서비스 복구 단계는 31일 05:00에 공식적으로 시작되었습니다. 캐싱 시스템과 마찬가지로, 초기 서비스 중단이나 문제 해결 단계 동안 가동 중이던 서비스의 상당 부분이 중단된 상태였습니다. 팀은 이러한 서비스를 적절한 용량 수준으로 재시작하고 정상적으로 작동하는지 확인해야 했습니다. 이 과정은 순조롭게 진행되었으며, 10:00까지 플레이어들에게 서비스를 개방할 준비가 되었습니다.
캐시가 초기화되고 시스템 상태에 대한 확신이 서지 않은 상황에서, 시스템이 다시 불안정한 상태로 빠질 수 있는 트래픽 폭주를 원치 않았습니다. 트래픽 폭주를 방지하기 위해 DNS 스티어링을 사용하여 Roblox에 접속할 수 있는 플레이어 수를 관리했습니다. 이를 통해 무작위로 선정된 일정 비율의 플레이어만 접속을 허용하고, 나머지 플레이어들은 계속해서 정적 유지보수 페이지로 리디렉션되도록 했습니다. 비율을 높일 때마다 데이터베이스 부하, 캐시 성능, 전반적인 시스템 안정성을 점검했습니다. 하루 종일 작업을 이어가며 접속 허용 비율을 약 10%씩 단계적으로 늘려갔습니다. 가장 열성적인 플레이어들이 우리의 DNS 스티어링 방식을 파악하고, 서비스 복구 시 “조기” 접속을 위해 트위터에서 이 정보를 공유하기 시작하는 모습을 보는 것은 즐거운 일이었습니다. 장애 발생 73시간 후인 일요일 16시 45분, 모든 플레이어에게 접속 권한이 부여되었고 로블록스는 완전히 정상 가동되었습니다.
정전 사태로 인한 추가 분석 및 변경 사항
10월 31일에 플레이어들이 로블록스로 복귀할 수 있게 되었지만, 로블록스와 HashiCorp는 그 다음 주 내내 서비스 중단 원인에 대한 이해를 계속해서 심화해 나갔습니다. 새로운 스트리밍 프로토콜의 구체적인 경합 문제가 확인되어 격리되었습니다. HashiCorp는 로블록스 사용량과 유사한 규모로 스트리밍 벤치마크를 수행한 바 있지만, 방대한 수의 스트림과 높은 이탈률이 결합되어 발생하는 이 특정 현상은 이전에 관찰된 적이 없었습니다. HashiCorp 엔지니어링 팀은 해당 경합 문제를 재현하기 위한 새로운 실험실 벤치마크를 구축하고 추가적인 확장성 테스트를 수행하고 있습니다. 또한 HashiCorp는 극한의 부하 상황에서도 경합을 방지하고 안정적인 성능을 보장하기 위해 스트리밍 시스템의 설계를 개선하는 데 주력하고 있습니다.
슬로우 리더(slow leader) 문제에 대한 추가 분석을 통해 2초의 Raft 데이터 쓰기 지연 및 클러스터 일관성 문제의 주요 원인도 밝혀졌습니다. 엔지니어들은 BoltDB의 내부 작동 방식을 더 잘 이해하기 위해 아래와 같은 플레임 그래프를 분석했습니다.


앞서 언급한 명령어 출력은 다음과 같은 몇 가지 사실을 알려줍니다:
- 이 4.2GB 로그 저장소에는 실제 데이터(모든 인덱스 내부 정보 포함)가 489MB만 저장되어 있습니다. 3.8GB는 “빈” 공간입니다.
- 프리리스트는 거의 백만 개의 빈 페이지 ID를 포함하고 있어 7.8MB입니다.
즉, 로그가 추가될 때마다(일정량의 데이터가 묶여 Raft에 기록될 때마다), 실제로 추가되는 원시 데이터는 16KB 이하였음에도 불구하고 7.8MB 크기의 새로운 프리리스트가 디스크에 함께 기록되고 있었습니다.
이러한 작업에 대한 역압(back pressure)은 TCP 버퍼를 가득 채우게 했으며, 상태가 좋지 않은 리더에서 2~3초의 쓰기 시간이 발생하는 원인이 되었습니다. 아래 이미지는 해당 사고 당시 TCP 제로 윈도우(Zero Windows)에 대한 연구 결과를 보여줍니다.

HashiCorp와 Roblox는 기존 BoltDB 툴링을 활용해 데이터베이스를 "압축"하는 프로세스를 개발 및 배포하여 성능 문제를 해결했습니다.
최근 개선 사항 및 향후 계획
서비스 중단 사태가 발생한 지 2.5개월이 지났습니다. 그동안 우리는 무엇을 해왔을까요? 우리는 이 기간 동안 서비스 중단 사태에서 최대한 많은 교훈을 얻고, 이를 바탕으로 엔지니어링 우선순위를 조정하며, 시스템을 적극적으로 강화하는 데 주력했습니다. 로블록스의 핵심 가치 중 하나는 '커뮤니티 존중(Respect The Community)'입니다. 사건 경과를 설명하는 게시물을 더 빨리 올릴 수도 있었지만, 게시하기 전에 시스템 안정성을 개선하는 데 상당한 진전을 이루는 것이 우리 커뮤니티인 여러분께 드려야 할 의무라고 생각했습니다.
완료되었거나 진행 중인 안정성 개선 사항의 전체 목록은 이 글에 담기에는 너무 길고 상세하지만, 주요 내용은 다음과 같습니다:
텔레메트리 개선
저희 텔레메트리 시스템과 Consul 간에 순환 의존성이 존재했습니다. 이는 Consul의 상태가 비정상적일 때, 문제 원인을 파악하는 데 도움이 될 텔레메트리 데이터를 확보하지 못했음을 의미합니다. 저희는 이 순환 의존성을 제거했습니다. 이제 텔레메트리 시스템은 모니터링 대상으로 설정된 시스템에 더 이상 의존하지 않습니다.
Consul 및 BoltDB 성능에 대한 가시성을 높이기 위해 텔레메트리 시스템을 확장했습니다. 이제 시스템이 이번 서비스 중단을 유발했던 상태에 근접하고 있다는 징후가 포착되면, 매우 구체적인 경고를 수신하게 됩니다. 또한 Roblox 서비스와 Consul 간의 트래픽 패턴에 대한 가시성을 높이기 위해 텔레메트리 시스템을 확장했습니다. 시스템의 동작과 성능에 대한 이러한 다층적인 추가 가시성은 이미 시스템 업그레이드 및 디버깅 과정에서 큰 도움이 되었습니다.
여러 가용 영역 및 데이터 센터로의 확장
모든 Roblox 백엔드 서비스를 단일 Consul 클러스터에서 운영하던 방식은 이번과 같은 유형의 서비스 중단에 취약했습니다. 이에 따라 백엔드 서비스를 호스팅할 지리적으로 분리된 추가 데이터 센터를 위한 서버 및 네트워크 인프라를 이미 구축했습니다. 현재 해당 데이터 센터 내 여러 가용 영역으로 서비스를 이전하기 위한 작업을 진행 중이며, 이를 가속화하기 위해 엔지니어링 로드맵과 인력 배치 계획을 대폭 수정했습니다.
Consul 업그레이드 및 샤딩
Roblox는 여전히 빠르게 성장하고 있으므로, 여러 Consul 클러스터를 운영하더라도 Consul에 가해지는 부하를 줄이고자 합니다. 서비스들이 Consul의 KV 스토어와 헬스 체크를 어떻게 사용하는지 검토한 결과, 일부 핵심 서비스를 전용 클러스터로 분리하여 중앙 Consul 클러스터의 부하를 더 안전한 수준으로 낮췄습니다.
일부 핵심 Roblox 서비스는 더 적합한 다른 스토리지 시스템이 있음에도 불구하고, 데이터 저장을 위한 편리한 장소로 Consul의 KV 스토어를 직접 사용하고 있습니다. 현재 이 데이터를 더 적합한 스토리지 시스템으로 마이그레이션하는 중입니다. 이 작업이 완료되면 Consul에 가해지는 부하도 줄어들 것입니다.
대량의 사용되지 않는 KV 데이터를 발견했습니다. 이 데이터를 삭제함으로써 Consul의 성능이 개선되었습니다.
HashiCorp와 긴밀히 협력하여, 무제한 프리리스트 증가 문제를 일으키지 않는 bbolt라는 후속 버전으로 BoltDB를 대체하는 새로운 버전의 Consul을 배포하고 있습니다. 연말 트래픽이 가장 많은 시기에 복잡한 업그레이드를 피하기 위해 이 작업을 의도적으로 새해로 미뤘습니다. 현재 업그레이드를 테스트 중이며 1분기에 완료될 예정입니다.
부트스트랩 절차 및 구성 관리 개선
서비스 복구 작업은 Roblox 서비스에 필요한 캐시의 배포 및 예열을 포함한 여러 요인으로 인해 지연되었습니다. 당사는 이 과정을 더욱 자동화하고 오류 발생 가능성을 줄이기 위해 새로운 도구와 프로세스를 개발 중입니다. 특히, 정지 상태에서 캐시 시스템을 신속하게 가동할 수 있도록 캐시 배포 메커니즘을 재설계했습니다. 이에 대한 구현이 진행 중입니다.
저희는 HashiCorp와 협력하여 장기간 서비스 중단 후 대규모 작업을 더 쉽게 가동할 수 있도록 Nomad의 여러 개선 사항을 확인했습니다. 이러한 개선 사항은 이달 말로 예정된 다음 Nomad 업그레이드의 일환으로 배포될 예정입니다.
서버 구성 변경을 더 빠르게 수행할 수 있는 메커니즘을 개발하여 배포했습니다.
스트리밍 재도입
당사는 원래 Consul 클러스터의 CPU 사용량과 네트워크 대역폭을 줄이기 위해 스트리밍을 도입했습니다. 새로운 구현 방식이 당사의 워크로드와 규모에서 테스트를 마치면, 이를 시스템에 신중하게 재도입할 계획입니다.
퍼블릭 클라우드에 대한 참고 사항
이번과 같은 서비스 중단 사태 이후, 로블록스가 퍼블릭 클라우드로 이전하여 제3자가 당사의 핵심 컴퓨팅, 스토리지 및 네트워킹 서비스를 관리하도록 할 것을 고려할지 묻는 것은 자연스러운 일입니다.
로블록스의 또 다른 핵심 가치 중 하나는 '장기적 관점(Take The Long View)'이며, 이 가치는 우리의 의사결정에 큰 영향을 미칩니다. 우리는 온프레미스 환경에서 자체적인 기반 인프라를 구축하고 관리합니다. 이는 현재의 규모뿐만 아니라, 더 중요한 것은 플랫폼이 성장함에 따라 도달하게 될 미래의 규모를 고려했을 때, 이것이 우리 비즈니스와 커뮤니티를 지원하는 최선의 방법이라고 믿기 때문입니다. 특히, 백엔드 및 네트워크 에지 서비스를 위해 자체 데이터 센터를 구축하고 관리함으로써, 퍼블릭 클라우드에 비해 비용을 상당히 절감할 수 있었습니다. 이러한 비용 절감 효과는 플랫폼 내 크리에이터들에게 지급할 수 있는 금액에 직접적인 영향을 미칩니다. 또한, 자체 하드웨어를 보유하고 에지 인프라를 구축함으로써 성능 변동을 최소화하고 전 세계 플레이어들의 지연 시간을 세심하게 관리할 수 있습니다. 일관된 성능과 낮은 지연 시간은 퍼블릭 클라우드 제공업체의 데이터 센터 근처에 거주하지 않는 플레이어들의 경험에 있어 매우 중요합니다.
단, 저희는 특정 접근 방식에 이념적으로 얽매여 있지 않습니다. 플레이어와 개발자에게 가장 합리적인 경우라면 퍼블릭 클라우드를 활용합니다. 예를 들어, 버스트 용량, DevOps 워크플로우의 상당 부분, 그리고 내부 분석의 대부분에는 퍼블릭 클라우드를 사용합니다. 일반적으로 퍼블릭 클라우드는 성능과 지연 시간이 결정적인 요소가 아니며 제한된 규모로 운영되는 애플리케이션에 적합한 도구라고 판단합니다. 그러나 성능과 지연 시간이 가장 중요한 워크로드의 경우, 자체 온프레미스 인프라를 구축하고 관리하기로 결정했습니다. 우리는 이 결정이 시간, 비용, 인력을 필요로 한다는 점을 인지하고 있지만, 이를 통해 더 나은 플랫폼을 구축할 수 있을 것이라는 확신도 가지고 있습니다. 이는 '장기적인 관점(Take The Long View)'이라는 우리의 가치와 일치합니다.
중단 사태 이후의 시스템 안정성
Roblox는 보통 12월 말에 트래픽 급증을 경험합니다. 아직 해결해야 할 안정성 관련 과제가 많이 남아 있지만, 12월 트래픽 급증 기간 동안 Roblox에서 단 한 건의 중대한 운영 사고도 발생하지 않았으며, 이 기간 동안 Consul과 Nomad의 성능 및 안정성이 모두 우수했다는 점을 기쁘게 보고합니다. 당장의 안정성 개선 조치가 이미 효과를 거두고 있는 것으로 보이며, 장기 프로젝트들이 마무리됨에 따라 더 나은 결과를 기대하고 있습니다.
마치며
전 세계 로블록스 커뮤니티 여러분의 이해와 지원에 깊이 감사드립니다. 로블록스의 핵심 가치 중 하나인 '책임감(Take Responsibility)'에 따라, 저희는 이번 사태에 대해 전적인 책임을 집니다. 또한 HashiCorp 팀에 다시 한번 진심으로 감사의 말씀을 전합니다. HashiCorp 엔지니어들은 이번 전례 없는 서비스 중단 사태가 발생하자마자 즉시 지원에 나섰으며, 끝까지 우리 곁을 지켜주었습니다. 서비스 중단 사태가 발생한 지 두 달이 지난 지금도, 로블록스와 HashiCorp 엔지니어들은 유사한 사태가 다시는 발생하지 않도록 함께 최선을 다하기 위해 긴밀히 협력하고 있습니다.
마지막으로, 로블록스가 일하기에 정말 훌륭한 곳임을 증명해 준 동료들에게 감사의 말씀을 전합니다. 로블록스는 예의와 존중을 중요하게 생각합니다. 일이 순조로울 때는 예의 바르고 존중하는 태도를 유지하기 쉽지만, 진정한 시험대는 상황이 어려워졌을 때 서로를 어떻게 대하느냐에 있습니다. 73시간에 걸친 서비스 중단 기간 중, 시간이 흐르고 스트레스가 쌓여가는 상황에서 누군가가 자제력을 잃거나 무례한 말을 하거나, 이 모든 것이 누구의 잘못인지 소리 내어 따지는 모습을 보더라도 전혀 놀랍지 않았을 것입니다. 하지만 실제로는 그런 일이 일어나지 않았습니다. 우리는 서로를 지지하며, 서비스가 정상화될 때까지 24시간 내내 하나의 팀으로서 함께 노력했습니다. 물론 이번 서비스 중단 사태와 그것이 커뮤니티에 미친 영향에 대해 자랑스럽게 생각하지는 않지만, 팀으로서 힘을 합쳐 로블록스를 다시 되살린 과정과 그 모든 단계에서 서로에게 예의 바르고 존중하는 태도를 보인 점에 대해서는 자부심을 느낍니다.
이번 경험을 통해 많은 것을 배웠으며, 앞으로 로블록스를 더욱 강력하고 안정적인 플랫폼으로 만들기 위해 그 어느 때보다 헌신하겠습니다.
다시 한번 감사드립니다.
¹ 이 블로그 게시물의 모든 날짜와 시간은 태평양 표준시(PST) 기준입니다.


