{"id":"IL-0566","org":"Devsisters","date":"2021-03-06","docs":["https://www.cockroachlabs.com/docs/v22.2/configure-replication-zones#replication-zone-variables","https://www.cockroachlabs.com/docs/v22.1/load-based-splitting","https://www.cockroachlabs.com/blog/sql-in-cockroachdb-mapping-table-data-to-key-value-storage/"],"title":{"en":"Hot ranges after a Cookie Run: Kingdom guild update drove CockroachDB nodes to 100% CPU, forcing emergency maintenance","ko":"쿠키런: 킹덤 길드 업데이트 직후 CockroachDB 노드 몇 대에 부하가 몰려 CPU가 100%에 이르고 게임에 에러가 났다"},"domain":["database","aws"],"impact":{"scope":"Cookie Run: Kingdom returned errors after the guild update, new logins were blocked for periods, and the game was in emergency maintenance from 14:50 until it reopened in the evening","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"In a single-server relational database a change to the primary-key prefix is usually harmless, but in CockroachDB's distributed design such a change on a production cluster needs great care. The team had studied this in CockroachDB documentation, but living through it taught far more than reading about it.","ko":"일반 RDBMS에서는 Primary Key prefix를 바꿔도 큰 문제가 되지 않지만, 분산 처리를 하는 CockroachDB의 프로덕션 클러스터에서는 이런 변경에 상당한 주의가 필요하다. 팀은 CockroachDB 스터디에서 문서와 자료를 읽어 이 내용을 알고 있었다. 그러나 직접 겪어 보니 글로 읽어 알던 것보다 훨씬 큰 일이었다."},"related":[],"sources":[{"url":"https://tech.devsisters.com/posts/crk-hot-range-postmortem/","kind":"blog","archived":"https://web.archive.org/web/20241107170144/https://tech.devsisters.com/posts/crk-hot-range-postmortem/"}],"summary":{"en":"CockroachDB stores data in units called ranges and splits or merges them on its own by size and queries per second; by default a range stays between 128MB and 512MB and below 2500 queries per second. Ranges are cut from primary keys in sorted order, so similar keys land in the same or neighbouring ranges, and a range that receives most of the workload, a hot range, leaves its few nodes carrying the whole load. In the Cookie Run: Kingdom guild update, which ended at 11:00 on 6 March 2021, the new guild tables were split in advance, but the root cause was an index prefix structure that was added and changed without the team noticing. Analytics tables written by the game server use primary keys of the form <feature>-<uuid>; they had never been split and had caused no problem since launch, because the tutorial spread players' first writes over time. The guild feature had almost no such funnel, so right after the update every player wrote huge numbers of guild-<uuid> keys before the system could split the range. In a second table, a refactoring changed the primary-key format, so only a small part of its already split ranges was used. Both tables existed before, had no schema change and took no direct gameplay traffic, so nobody expected hot ranges.\n\nCockroachDB's load-based rebalancing could have fixed this over time, but the node added during the maintenance made CockroachDB move ranges onto it, and moving ranges uses the same queue as splitting them. The replication queue had not drained by the reopening, so the hot ranges could only be spread after the new node had received its share. From about 13:00 the team used the game's login block without kicking players as a makeshift queue, since the game had no access queue; at 14:50, when several nodes failed health checks, it put the game into full emergency maintenance, and the cluster improved at once. Because full backups stalled, the team backed up the game data table by table, with help from CockroachDB support, paused writing of play records, and from about 18:30 split the problem tables while waiting for the replication queue to reach zero. A node that failed its Host Status Check at about 19:20 and logged filemap_fdatawait_range_keep_errors was decommissioned, after which no range was under-replicated and cluster metrics returned to normal.\n\nThe game reopened around 22:00 ahead of the midnight login-bonus rush. One node reached 100% CPU, was calmed with the login-block queue, and soon did it again; because it was always the same node, the problem was most likely the node itself, and terminating it ended the incident.","ko":"CockroachDB는 Primary Key를 정렬해 일정 간격으로 나눈 Range 단위로 데이터를 저장하고, 쿼리가 몰리는 Range는 스스로 나눠 여러 노드에 분산한다. 비슷한 키에 작업이 몰려 한 Range를 맡은 노드만 부하를 지는 것을 Hot Range라고 한다. 2021년 3월 6일 쿠키런: 킹덤 길드 업데이트 점검에서 팀은 새 길드 테이블의 Range를 미리 강제로 나눠 클러스터 전체에 분산했다. 며칠 전 물리 장비 문제로 빠진 데이터베이스 노드를 대신할 새 노드도 함께 추가했다. 점검은 11시에 끝났다.\n\n근본 원인은 팀이 미리 알지 못한 인덱스 prefix 구조의 추가와 변경이었다. 분석용 데이터를 쓰는 테이블은 <feature>-<uuid> 형식의 Primary Key를 쓰는데, 출시 초기에는 튜토리얼 때문에 유저마다 쓰기 시점이 달라 미리 나누지 않아도 문제가 없었다. 길드 기능은 모든 유저가 기다리던 기능이라 업데이트 직후 guild-<uuid> 키가 한꺼번에 쓰였고, 자동 Split이 따라가지 못해 Hot Range가 생겼다. 리팩토링으로 Primary Key 형식이 바뀐 다른 테이블은 이미 나뉘어 있던 Range 중 극히 일부만 쓰게 돼 역시 Hot Range가 생겼다. 이 테이블들은 스키마 변경이 없어 눈에 띄지 않았고, 플레이 트래픽을 직접 받지 않아 Hot Range를 예상하지 못했다. 게다가 새 노드를 추가하며 생긴 Range 이동 작업이 Range 분할과 같은 Replication Queue를 썼고, 이 큐가 서비스를 열 때까지 비지 않아 Hot Range가 분산되지 못했다.\n\n접속 대기열이 없었던 팀은 기존 유저를 내보내지 않고 새 로그인만 막는 방식으로 접속자 수를 조절했다. 오후 2시 50분 노드 여러 대가 health check를 통과하지 못하자 전체 긴급 점검에 들어갔고, 점검 직후 클러스터 상태가 눈에 띄게 좋아졌다. 전체 데이터베이스 백업이 멈추자 테이블 단위로 백업해 게임 데이터를 지켰다. 플레이 기록 저장을 멈추고 오후 6시 반쯤 문제 테이블을 Split했으며, 오후 7시 20분에 Host Status Check에 실패한 노드를 Decommission해 복제되지 않은 Range를 0으로 만들었다. 22시에 다시 연 뒤에도 노드 하나가 거듭 CPU 100%에 이르렀다. 팀은 노드 자체의 문제로 보고 그 노드를 종료해 문제를 끝냈다."},"trigger":{"en":"During the guild update maintenance, new guild tables were added and pre-split across the cluster, and a new database node was added to replace one removed a few days earlier for a physical hardware issue. When the game reopened, all players at once wrote to existing tables whose primary-key prefixes were new or had changed in the update.","ko":"길드 업데이트 점검 뒤 서비스를 열자 모든 유저가 곧바로 새 기능을 썼고, 분석용 테이블에 guild-<uuid> 형식의 Primary Key가 매우 빠르게 쓰였다. 리팩토링으로 Primary Key 형식이 바뀐 테이블도 있었다."},"severity":"unknown","symptoms":["uneven-load","database-overloaded","high-error-rate"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"Monitoring after the maintenance showed one node's CPU at the ceiling together with errors in server metrics. Later a Slack alert reported a node failing its Host Status Check, and the abnormal-log channel reported filemap_fdatawait_range_keep_errors.","ko":"모니터링 중 한 노드의 CPU 사용량이 최대치에 이르렀다는 소식과 함께 서버 지표에서 에러가 관측돼 알았다. 이후 노드 문제는 Host Status Check 실패 슬랙 알림과 이상 로그 감지 채널의 알림으로 알았다."},"components":["database"],"root_cause":"deploy-rollout","remediation":[{"text":{"en":"Blocked new logins without kicking players already in the game, using the maintenance switch as a makeshift access queue","ko":"기존 유저를 내보내지 않고 새 로그인만 막아 입장 인원을 0명으로 둔 대기열처럼 썼고, 지표를 보며 막고 풀기를 반복해 동시 접속자 수를 조절했다."}},{"text":{"en":"Put the game into full emergency maintenance at 14:50 when several nodes failed health checks","ko":"오후 2시 50분 노드 여러 대가 health check를 통과하지 못하자 아무도 게임을 할 수 없는 전체 긴급 점검에 들어갔다."}},{"text":{"en":"Backed up the game data table by table after the full database backup stalled","ko":"전체 데이터베이스 백업이 멈추자 테이블 단위로 백업했다. 부하가 몰린 것은 특정 테이블의 일부 Range였고 그 테이블은 다른 테이블로 복구할 수 있었기 때문이다."}},{"text":{"en":"Paused writing play records to reduce load on the database before reopening","ko":"서비스를 열기 전에 데이터베이스 부하를 최대한 줄이려고 플레이 기록 저장을 잠시 멈췄다."}},{"text":{"en":"Split the problem tables from about 18:30","ko":"오후 6시 반쯤 문제가 된 테이블들에 Split 작업을 시작해 부하가 모든 노드에 고르게 나뉘도록 했다."}},{"text":{"en":"Decommissioned the node that failed its Host Status Check","ko":"Host Status Check에 실패하고 filemap_fdatawait_range_keep_errors 로그가 나온 노드를 클러스터에서 빼기로 하고, Range 분리와 그 노드의 Decommission을 함께 진행했다. Decommission이 끝나자 복제되지 않은 Range가 0이 되고 지표가 안정됐다."}},{"text":{"en":"Reopened the game and used the login-block queue again when a node reached 100% CPU","ko":"서비스를 다시 연 뒤 한 노드가 CPU를 100% 쓰자 다시 대기열 방식으로 접속을 조절해 CPU 사용량을 안정시켰다."}},{"text":{"en":"Terminated the node that kept returning to 100% CPU","ko":"같은 노드가 거듭 CPU 100%에 이르러 노드 자체의 문제로 보고 그 노드를 종료해 문제를 끝냈다."}}],"action_items":[],"contributing":["assumption-unverified","overlapping-failures"],"recurrence_of":null,"symptoms_text":{"en":"About half an hour after the Cookie Run: Kingdom guild update maintenance ended at 11:00 on 6 March 2021, one CockroachDB node was using all 24 cores of its 24-core machine and server metrics began to show a significant number of errors. Soon two more nodes showed abnormal CPU use.\n\nRight after the maintenance, database CPU had looked a little higher than expected, but with more players online than usual and a new interactive feature, the team judged it nothing to worry about. Once single nodes hit the ceiling, it was clear that load was not being spread evenly and that the overloaded nodes were hurting the stability of the whole database cluster.\n\nBlocking new logins while letting players already in the game continue calmed the cluster for a while, but at 14:50 several nodes suddenly started failing health checks, more nodes than right after the maintenance. During the instability CockroachDB marked some ranges Unavailable, blocking access because it could not guarantee their consistency. A full database backup with the BACKUP statement stalled partway, and an export to CSV made the cluster less stable and was stopped.\n\nLater, at about 19:20, a Slack alert reported that a database node had failed its Host Status Check, and the channel that watches for abnormal server logs reported filemap_fdatawait_range_keep_errors from the same instance. After the game reopened, one database node reached 100% CPU, and after it was calmed with the login block, the same node went back to 100% CPU again and again.","ko":"2021년 3월 6일 11시 길드 업데이트 점검을 마친 뒤 데이터베이스 CPU 사용량이 예상보다 조금 높았다. 11시 30분쯤 24코어 머신인 데이터베이스 노드 하나가 24코어를 모두 쓰며 CPU 사용량이 최대치에 이르렀고, 같은 시각 서버 지표에서 상당한 양의 에러가 보이기 시작했다. 곧 다른 두 노드에서도 CPU 사용량 이상이 나타났다.\n\n점검 직후 CPU가 높았을 때 팀은 접속자가 평소보다 많고 유저끼리 상호작용하는 새 기능이 들어온 탓으로 보고 걱정할 일이 아니라고 판단했다. 노드 세 대가 과부하에 걸리자 팀은 부하가 노드에 고르게 나뉘지 않고 있다는 사실을 인정해야 했다.\n\n로그인만 막아 접속자 수를 조절하자 클러스터가 꽤 안정됐지만, 오후 2시 50분 클러스터 노드 여러 대가 갑자기 health check를 통과하지 못했다. 점검 직후보다 더 많은 노드가 영향을 받았다. 클러스터가 불안정해지면서 CockroachDB가 데이터 정합성을 보증할 수 없어 접근을 막은 Unavailable Range도 생겼다.\n\n전체 점검에 들어간 뒤 BACKUP statement로 전체 데이터베이스를 백업하려 했지만 백업이 어느 시점에서 멈췄다. CSV 파일로 export를 시도하자 지표상 클러스터가 불안정해져 중단했다.\n\n오후 7시 20분쯤 데이터베이스 노드 한 대가 Host Status Check를 통과하지 못했다는 슬랙 알림이 왔다. 이상 로그를 감지하는 채널에는 같은 인스턴스에서 filemap_fdatawait_range_keep_errors 로그가 감지됐다는 알림이 왔다.\n\n22시에 서비스를 다시 연 뒤에도 데이터베이스 노드 하나가 CPU를 100% 썼다. 대기열로 안정시켜도 같은 노드가 다시 CPU 100%에 이르기를 되풀이했다."},"updated_at":"2026-11-14T12:34:21.422103Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0566","org_slug":"devsisters"}