{"id":"IL-0213","org":"Roblox","date":"2021-10-28","docs":[],"title":{"en":"Roblox was down for 73 hours because key-value writes on its single Consul cluster slowed from under 300 ms to 2 seconds","ko":"Consul 클러스터의 KV 쓰기가 느려지고 리더가 뒤처지며 Roblox 전체가 73시간 동안 멈췄다"},"domain":["database"],"impact":{"scope":"Roblox fully down: online players fell to 50% of normal at 16:35 on 28 October and the system then went fully down, until all players were let back in at 16:45 on 31 October; no user data was lost","users":"Roblox players; fifty million players use Roblox every day","region":"Roblox data centers"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Two mostly unrelated problems deep inside Consul, contention in streaming under very high read and write load and BoltDB freelist growth, were hard to diagnose, and that difficulty was the main reason the outage lasted so long. A single Consul cluster serving every workload made the impact larger, and monitoring that depended on Consul hid the cause when it was needed most. Faster hardware did not help and may have hurt, because the contention was worse on NUMA machines. HashiCorp had benchmarked streaming at a similar scale, but the problem needed both a large number of streams and a high churn rate. Restarting a platform from a fully stopped state, including warming caches, took a long time and needed tools built for bootstrapping rather than incremental change.","ko":"Roblox 는 원인 두 가지가 Consul 구현 깊숙이 묻혀 있어 진단이 어려웠던 것이 장애를 길게 만든 주된 이유라고 봤다. 원인을 보여 줄 모니터링 시스템이 장애가 난 Consul 에 기대고 있어 분석이 크게 막혔다. 모든 백엔드 서비스를 Consul 클러스터 하나에 올린 구성이 이런 장애에 취약했다.\n\nHashiCorp 는 Roblox 와 비슷한 규모에서 streaming 을 벤치마크했지만, 이 경합은 스트림 수가 많고 교체가 잦은 조건이 겹칠 때만 나타나 전에는 보지 못했다. 더 빠른 하드웨어는 도움이 되지 않았고 오히려 안정성을 해쳤을 수 있다. 오래 완전히 멈춘 상태에서 서비스를 올리는 데도 캐시 배포와 데우기 때문에 시간이 많이 걸렸다."},"related":[],"sources":[{"url":"https://about.roblox.com/newsroom/2022/01/roblox-return-to-service-10-28-10-31-2021","kind":"blog","archived":"https://web.archive.org/web/20261104021028/https://about.roblox.com/newsroom/2022/01/roblox-return-to-service-10-28-10-31-2021"}],"summary":{"en":"Roblox runs over 18,000 servers and 170,000 containers in its own data centers on the HashiStack: Nomad schedules containers, Vault holds secrets, and Consul provides service discovery, health checks, session locking and a KV store. One Consul cluster of 5 voters and 5 read-only non-voters, with a leader elected by Raft, served all of the backend. In the months before, Roblox had upgraded from Consul 1.9 to 1.10 for its streaming feature and had been enabling streaming service by service; on 27 October it was enabled for traffic routing, whose node count also grew by 50%. Under that combination of very high read and write load, streaming, which uses fewer Go channels than long polling, concentrated contention on a single Go channel and blocked writes. The dual-socket NUMA 128-core servers that responders moved to made this contention worse.\n\nFrom 28 October the team, joined by HashiCorp engineers, tried four diagnoses: degraded hardware, too much traffic, thousands of containers reconnecting, and the move to 128-core machines. Replacing nodes, restoring the cluster from a snapshot behind iptables, disabling all non-essential Consul use, and returning to 64-core servers each failed to bring KV write latency down. On 30 October perf reports and flame graphs pointed to the streaming code paths; streaming was disabled for all Consul systems, and when the change finished propagating at 15:51 the 50th percentile KV write latency fell to 300ms. Some leaders were still slow, so the team kept those servers from staying elected. HashiCorp found the reason after the outage: Consul stores its Raft log in BoltDB, whose file never shrinks after old entries are deleted, and under Roblox's workload the freelist of free pages grew so large that every 16kB append also wrote a 7.8MB freelist to disk.\n\nWith Consul stable 54 hours into the outage, the team redeployed the caching system, which normally handles 1B requests per second. Its scheduling data in the Consul KV store was wrong, likely because of the snapshot reset, the job scheduler kept placing cache jobs on an unhealthy node it saw as empty, and the cache deployment tool was not built to bootstrap a large cluster from nothing. By 05:00 on 31 October, 61 hours in, Consul and the caches were healthy. Services were restarted at the right capacity, and from 10:00 players were let in with DNS steering in roughly 10% steps while database load, cache performance and stability were checked. At 16:45, 73 hours after the start, all players had access. No user data was lost.","ko":"Roblox 는 자체 데이터센터의 18,000대가 넘는 서버와 170,000개의 컨테이너를 Nomad, Consul, Vault 로 운영한다. Consul 은 서비스 디스커버리, 헬스 체크, 세션 잠금, KV 저장소를 맡고, 투표권이 있는 voter 5대와 읽기 전용 non-voter 5대로 이루어지며 Raft 로 리더를 뽑는다. 사고 몇 달 전 Roblox 는 CPU 와 네트워크 사용을 줄이는 streaming 기능을 쓰려고 Consul 1.9 에서 1.10 으로 올렸고, 백엔드 서비스에 차례로 켜 왔다. 10월 27일 14:00 PST 에 트래픽 라우팅 서비스에도 streaming 을 켜고 라우팅 노드를 50% 늘렸다.\n\n10월 28일 13:37 PST 에 Vault 성능이 떨어지고 Consul KV 쓰기 지연이 2초로 올랐으며, 16:35 에 플레이어가 절반으로 줄고 시스템 전체가 멈췄다. Consul 이 단일 장애 지점이었기 때문이다. HashiCorp 엔지니어가 합류한 대응 팀은 느린 하드웨어, 트래픽 증가, 재접속하는 컨테이너의 부하를 차례로 의심했다. 노드를 교체하고, 128코어 서버로 옮기고, 스냅샷으로 클러스터를 초기화하고, 외부 트래픽을 막고 꼭 필요하지 않은 Consul 사용을 끄고, 64코어 서버로 되돌렸지만 모두 소용없었다. 텔레메트리 시스템도 Consul 에 기대고 있어 원인을 찾는 데 필요한 데이터가 없었다.\n\n10월 30일 perf report 와 플레임 그래프에서 streaming 코드 경로가 경합을 일으킨다는 증거를 찾았다. streaming 은 롱 폴링보다 동시성 제어 요소인 Go 채널을 적게 써서, 읽기와 쓰기 부하가 모두 매우 높으면 채널 하나에 경합이 몰려 쓰기가 막혔다. NUMA 구조의 2소켓 128코어 서버에서는 경합이 더 심해졌다. 모든 Consul 시스템에서 streaming 을 끄자 15:51 에 KV 쓰기의 p50 지연이 300ms 로 내려갔다. 일부 리더가 여전히 느려 그런 리더가 뽑힌 채로 남지 않게 막았다. 사고 뒤 HashiCorp 는 Raft 로그를 담는 BoltDB 의 freelist 문제가 원인임을 밝혔다. 4.2GB 로그 저장소에 실제 데이터는 489MB 뿐이었고 freelist 가 7.8MB 라서, 16kB 이하를 쓸 때마다 7.8MB freelist 를 디스크에 새로 썼다.\n\nConsul 이 안정된 뒤 대응 팀은 평소 초당 10억 건을 처리하는 캐시 시스템을 다시 배포했다. 스냅샷 초기화로 Consul KV 의 스케줄링 데이터가 틀어져 있었다. 잡 스케줄러는 unhealthy 노드를 비어 있는 노드로 봐서 캐시 작업을 몰아넣었고, 배포 도구는 처음부터 큰 클러스터를 세우는 용도가 아니었다. 10월 31일 05:00 에 Consul 과 캐시가 정상이 됐다. 대응 팀은 서비스들을 알맞은 용량으로 다시 띄운 뒤 DNS 조정으로 무작위로 고른 플레이어를 약 10% 씩 늘려 들였다. 16:45 PST 에 모든 플레이어가 들어와 73시간 만에 완전히 복구됐다. 사용자 데이터 손실이나 무단 접근은 없었다."},"trigger":{"en":"On 27 October at 14:00, one day before the outage, Roblox enabled the Consul streaming feature on the backend service responsible for traffic routing and, to prepare for end-of-year traffic, added 50% more nodes to traffic routing.","ko":"10월 27일 14:00 PST 에 트래픽 라우팅을 맡는 백엔드 서비스에 Consul 의 새 streaming 기능을 켰다. 같은 롤아웃에서 연말 트래픽에 대비해 트래픽 라우팅 노드 수를 50% 늘렸고, 하루 뒤 Consul 성능이 떨어지기 시작했다."},"severity":"unknown","symptoms":["high-latency","node-high-cpu","service-unavailable","monitoring-down"],"timeline":{"detected_after":null,"resolved_after":"73h"},"date_kind":"incident","detection":{"en":"Roblox engineers began investigating when Vault performance degraded and a single Consul server had high CPU load, before players were affected. Consul cluster metrics then showed elevated KV write latency.","ko":"10월 28일 13:37 PST 에 Vault 성능 저하와 Consul 서버 한 대의 높은 CPU 부하를 보고 Roblox 엔지니어들이 조사를 시작했다. Consul 클러스터 지표에서 KV 쓰기 지연이 올라간 것을 확인했다."},"components":["coordination-store","service-discovery","cache"],"root_cause":"software-bug","remediation":[{"text":{"en":"Replaced one Consul cluster node on the suspicion of degraded hardware","ko":"하드웨어 성능 저하를 의심해 Consul 클러스터 노드 한 대를 교체했다."}},{"text":{"en":"Replaced all Consul nodes with 128-core machines with faster NVMe SSDs","ko":"트래픽 증가를 의심해 Consul 클러스터의 모든 노드를 128코어와 더 빠른 NVME SSD 를 단 서버로 바꿨다."}},{"text":{"en":"Shut down the Consul cluster and reset its state from a snapshot taken at the start of the outage, with access blocked by iptables","ko":"Consul 클러스터 전체를 내리고 장애 시작 무렵인 몇 시간 전 스냅샷으로 상태를 되돌렸으며, iptables 로 접근을 막았다가 천천히 풀었다."}},{"text":{"en":"Blocked external traffic, disabled all non-essential Consul use, scaled services down and lowered health check frequency from 60 seconds to 10 minutes","ko":"외부 트래픽을 막고, Consul 을 쓰는 서비스 목록을 모아 꼭 필요하지 않은 사용을 끄는 설정을 배포했으며, 서비스 인스턴스를 한 자릿수로 줄이고 헬스 체크 간격을 60초에서 10분으로 늘렸다."}},{"text":{"en":"Moved the Consul cluster back to 64-core servers","ko":"경합이 128코어 서버로 옮긴 탓일 수 있다고 보고 Consul 클러스터를 다시 64코어 서버로 옮겼다."}},{"text":{"en":"Disabled the streaming feature for all Consul systems, which brought KV write latency back to 300ms","ko":"트래픽 라우팅 노드를 포함한 모든 Consul 시스템에서 streaming 기능을 껐고, 설정이 15:51 PST 에 다 퍼지자 KV 쓰기의 p50 지연이 300ms 로 내려갔다."}},{"text":{"en":"Kept the slow Consul servers from staying elected as leader","ko":"느린 리더 문제의 원인을 모르는 채로, 문제가 있는 서버가 리더로 뽑힌 채 남지 않게 막았다."}},{"text":{"en":"Redeployed the caching system and fixed its wrong scheduling data and failed deployments","ko":"캐시 시스템을 다시 배포했고, 틀어진 스케줄링 데이터와 끝나지 않는 배포 같은 문제를 밤새 찾아 고쳤다."}},{"text":{"en":"Restarted the stopped services at the right capacity levels","ko":"장애와 문제 분석 중에 내렸던 서비스들을 알맞은 용량으로 다시 띄우고 제대로 도는지 확인했다."}},{"text":{"en":"Let players back in with DNS steering in roughly 10% steps while checking database load, cache performance and stability","ko":"DNS 조정으로 무작위로 고른 일정 비율의 플레이어만 들이고 나머지는 정적 점검 페이지로 보냈으며, 데이터베이스 부하와 캐시 성능을 보며 약 10% 씩 늘렸다."}}],"action_items":[{"text":{"en":"HashiCorp builds laboratory benchmarks and scale tests that reproduce the streaming contention","ko":"HashiCorp 는 시험 환경에서 이 경합 문제를 재현하는 새 벤치마크를 만들고 대규모 시험을 늘린다."}},{"text":{"en":"HashiCorp improves the design of streaming so it does not contend under extreme load","ko":"HashiCorp 는 극한 부하에서도 경합이 생기지 않도록 streaming 시스템의 설계를 고친다."}},{"text":{"en":"Compact the BoltDB database with existing BoltDB tooling","ko":"기존 BoltDB 도구로 데이터베이스를 압축하는 절차를 만들어 배포했고, 이것으로 성능 문제가 풀렸다."}},{"text":{"en":"Remove the circular dependency between the telemetry systems and Consul","ko":"텔레메트리 시스템과 Consul 사이의 순환 의존을 없애, 텔레메트리 시스템이 감시 대상 시스템에 기대지 않게 했다."}},{"text":{"en":"Extend telemetry with targeted alerts on Consul and BoltDB performance and with visibility into traffic between services and Consul","ko":"Consul 과 BoltDB 성능, Roblox 서비스와 Consul 사이의 트래픽 패턴을 더 잘 보이게 텔레메트리를 넓혔고, 이번 장애 상태에 가까워지는 신호에 정밀한 알림을 건다."}},{"text":{"en":"Run the backend in an additional, geographically distinct data center and in multiple availability zones","ko":"백엔드 서비스를 둘 지리적으로 떨어진 데이터센터를 하나 더 세웠고, 데이터센터 안에서 여러 가용 영역으로 옮긴다."}},{"text":{"en":"Split critical services into their own dedicated Consul clusters","ko":"서비스들의 Consul KV 와 헬스 체크 사용을 검토해 일부 핵심 서비스를 전용 클러스터로 나눠 중앙 Consul 클러스터의 부하를 줄였다."}},{"text":{"en":"Move data that core services keep directly in the Consul KV store to a more suitable storage system","ko":"Consul KV 를 데이터 저장소로 쓰던 핵심 서비스의 데이터를 더 알맞은 저장 시스템으로 옮긴다."}},{"text":{"en":"Delete obsolete KV data from Consul","ko":"쓰지 않는 KV 데이터를 대량으로 찾아 지웠고, Consul 성능이 좋아졌다."}},{"text":{"en":"Upgrade to a Consul version that replaces BoltDB with bbolt, in Q1","ko":"HashiCorp 와 함께 BoltDB 대신 freelist 가 끝없이 커지는 문제가 없는 후속 라이브러리 bbolt 를 쓰는 새 Consul 버전을 배포한다. 연말 최대 트래픽을 피해 새해로 미뤘고 1분기에 끝낸다."}},{"text":{"en":"Redesign cache deployment so the cache system can be brought up quickly from a standing start","ko":"캐시 배포 방식을 다시 설계해 아무것도 없는 상태에서도 캐시 시스템을 빨리 올릴 수 있게 한다."}},{"text":{"en":"Deploy Nomad enhancements that make it easier to turn up large jobs after a long period of unavailability","ko":"오래 멈춘 뒤 큰 작업을 다시 올리기 쉽게 하는 Nomad 개선 몇 가지를 HashiCorp 와 찾았고, 다음 Nomad 업그레이드에서 배포한다."}},{"text":{"en":"Use mechanisms for faster machine configuration changes","ko":"서버 설정을 더 빨리 바꾸는 방법을 만들어 배포했다."}},{"text":{"en":"Reintroduce streaming only after a new implementation has been tested at Roblox's scale and workload","ko":"새 streaming 구현을 Roblox 규모와 부하로 시험한 뒤에 조심스럽게 다시 들인다."}}],"contributing":["single-point-of-failure","overlapping-failures","monitoring-blind-spot","manual-action-worsened","delayed-effect-change"],"recurrence_of":null,"symptoms_text":{"en":"On the afternoon of 28 October 2021 (13:37 PST), Vault performance at Roblox degraded and a single Consul server showed high CPU load. Consul cluster metrics showed that writes to its key-value (KV) store had a 50th percentile latency of 2 seconds instead of the usual under 300 ms. Players were not yet affected.\n\nAt 16:35 the number of online players fell to 50% of normal, and the platform then went fully down. Roblox services find each other through Consul, and Nomad and Vault depend on it as well, so with Consul unhealthy servers struggled to connect, new containers could not be scheduled and production secrets could not be fetched. The monitoring that would have shown the cause also depended on Consul, so responders lacked telemetry.\n\nThe first fixes treated the symptom as slow hardware and then as too much load. Replacing one Consul node, then moving the whole cluster to machines with 128 cores and faster NVMe SSDs, left the cluster reporting that most nodes could not keep up with writes, with KV write latency still around 2 seconds. A new symptom appeared: the Consul leader was regularly out of sync with the other voters. Resetting the cluster from a snapshot behind an iptables block looked healthy until the block was lifted and internal services' discovery and health check traffic returned; KV write latency climbed back to 2 seconds and services that depended on Consul began marking themselves unhealthy. Scaling services down to single-digit instances and cutting health checks from every 60 seconds to every 10 minutes also held only until 02:00 on 30 October, and going back to 64-core servers did not help.\n\nDebug logs and operating system metrics then showed Consul KV writes blocked for long periods, and a perf report showed most time spent in kernel spin locks on the Streaming subscription code path. After streaming was disabled, some newly elected leaders still showed the old latency. BoltDB statistics later showed a 4.2GB Raft log store holding only 489MB of data, with 3.8GB of empty space and a 7.8MB freelist of nearly a million free page ids written to disk on every log append; full TCP buffers and TCP zero windows went with 2-3s write times on the unhealthy leaders.","ko":"2021년 10월 28일 13:37 PST 에 Vault 성능이 떨어지고 Consul 서버 한 대의 CPU 부하가 높아졌다. 이때는 플레이어에게 영향이 없었다.\n\nConsul 클러스터 지표에서 Consul 이 데이터를 담는 KV 저장소의 쓰기 지연이 올라갔다. 이 작업의 p50 지연은 평소 300ms 아래였는데 2초가 됐다. 클러스터는 노드 대부분이 쓰기를 따라가지 못한다고 보고했다.\n\n16:35 에 동시 접속 플레이어 수가 평소의 50% 로 떨어졌고, 이어 시스템 전체가 멈췄다. Roblox 서비스는 서로를 찾을 때 Consul 에 기대고 Nomad 와 Vault 도 Consul 에 기댄다. 그래서 서버끼리 연결이 어려워졌고, 새 컨테이너를 스케줄하거나 인증에 쓰는 운영 시크릿을 가져올 수도 없었다. 원인을 보여 줄 모니터링 시스템도 Consul 에 기대고 있어 대응 팀은 텔레메트리 없이 원인을 찾아야 했다.\n\n대응 팀은 먼저 하드웨어가 느려졌다고 의심해 Consul 노드 한 대를 바꿨지만 나아지지 않았다. 다음에는 트래픽 증가를 의심해 모든 노드를 코어 수가 두 배인 128코어와 더 빠른 NVME SSD 를 단 서버로 바꿨지만 KV 쓰기의 p50 지연은 여전히 2초 안팎이었다.\n\nConsul 리더가 투표권을 가진 다른 서버인 voter 들과 자주 동기화가 어긋나는 증상이 새로 나타났고, 원인을 설명할 수 없었다. 대응 팀은 장애 시작 무렵의 스냅샷으로 클러스터를 되돌리고 iptables 로 접근을 막았다가 풀었다. 그러자 내부 서비스의 서비스 디스커버리와 헬스 체크 부하가 돌아오면서 KV 쓰기 지연이 다시 2초로 올랐고 Consul 에 기대는 서비스들이 스스로를 unhealthy 로 표시했다.\n\n대응 팀은 모든 외부 트래픽을 막고, 꼭 필요하지 않은 Consul 사용을 끄고, 서비스 인스턴스를 한 자릿수로 줄이고, 헬스 체크 간격을 60초에서 10분으로 늘린 뒤 서비스를 다시 올렸다. 그래도 10월 30일 02:00 에 Consul 은 훨씬 적은 부하에서 다시 나빠졌다. 64코어 서버로 되돌려도 나아지지 않았다.\n\n디버그 로그와 운영체제 지표에서 Consul KV 쓰기가 경합 때문에 오랫동안 막혀 있는 것이 보였다. perf report 에서는 대부분의 시간이 Streaming 구독 코드 경로의 커널 스핀락에서 쓰였다. streaming 을 끈 뒤에도 일부 서버가 리더로 뽑히면 같은 지연이 나타났고, 이런 리더에서는 TCP 버퍼가 가득 차 TCP Zero Window 가 생기고 쓰기가 2~3초 걸렸다. 나중에 본 BoltDB 통계에서 Raft 로그를 담는 4.2GB 저장소에 실제 데이터는 489MB 뿐이고 3.8GB 가 빈 공간이었다. BoltDB 는 로그를 덧붙일 때마다 빈 페이지 id 가 백만 개 가까이 든 7.8MB 짜리 빈 페이지 목록인 freelist 를 디스크에 새로 썼다."},"updated_at":"2026-11-14T05:53:42.102682Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0213","org_slug":"roblox"}