{"id":"IL-0565","org":"Devsisters","date":"2022-12-22","docs":[],"title":{"en":"Cookie Run: Kingdom's CockroachDB cluster failed when 16 of 24 nodes lost their filesystems, causing a 36-hour outage","ko":"쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다"},"domain":["database","aws"],"impact":{"scope":"Cookie Run: Kingdom was in emergency maintenance for 36 hours 29 minutes in total; the CockroachDB cluster holding user data was lost and rebuilt from data extracted from the surviving nodes","users":"Cookie Run: Kingdom players; the restored data belonged to users numbering in the millions","region":"AWS Tokyo region"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Because a person's mistake caused the failure, Devsisters changed its process for infrastructure work rather than relying on individual care: even experienced engineers make mistakes, so the prevention has to be built into the system. The author credits the recovery to an incident policy that does not blame the individual and asks for detailed records, to past incidents that were written down so people without the experience knew what to do, and to having infrastructure operations and data analysis teams with the needed infrastructure and skills in one organisation.","ko":"숙련된 작업자도 사람인 이상 실수할 수 있으므로, 실수를 막으려면 사람에게 기대지 말고 시스템을 갖춰야 한다. 복구할 수 있었던 바탕은 세 가지였다. 작업자 개인을 탓하지 않고 대응 과정을 기록해 액션 아이템을 정하는 장애 대응 원칙이 있었고, 과거 장애 경험이 기록으로 쌓여 있었으며, 인프라 운영과 데이터 분석을 맡는 팀이 한 조직에 있었다."},"related":[],"sources":[{"url":"https://tech.devsisters.com/posts/crk-launch-storage-postmortem/","kind":"blog","archived":null}],"summary":{"en":"Cookie Run: Kingdom stored its user data in CockroachDB on AWS EC2 m5d.8xlarge instances, a type with two local storage devices, with 24 nodes and data configured to keep 7 replicas. About four days after launch, storage use was rising fast, and a full disk on Linux would have stopped all writes. To prepare, the team ran a script that creates a ballast file, a dummy file that reserves space and can be deleted in an emergency. Because of a configuration issue, the script's path was wrong on nodes where it matched the primary mount, which EC2 picks at random among several local devices; the command overwrote the partition, and 16 of 24 nodes lost a usable filesystem. With more than a majority of nodes gone, the cluster stopped. AWS support could not help, and CockroachDB support advised restoring from the last backup, which would have lost all data written since that backup. Devsisters and the game team decided not to roll back user data and worked on three plans at once: repair the filesystems (Plan A), extract the data from the storage layer of the surviving nodes (Plan B), and replay analytics logs on top of the latest backup (Plan C, started for the most important server APIs).\n\nPlan A failed: the filesystems could not be repaired, and the 7 remaining nodes could not form a cluster because of the Raft quorum check at start-up. Plan B worked. The team estimated the chance of fully losing data at C(16, 7) / C(24, 7), about 3.3%, found a debug command in the CockroachDB source that shows the inside of SST files, decoded their format, and by about 4:00 the next day had Spark code that converted SST files to CSV. A modified CockroachDB CLI made the conversion more than 10 times faster than the estimated 24 hours on a 150-machine Spark cluster. From about 10:30 the team converted about 7.5TB of SST files from the 7 surviving nodes. IMPORT INTO takes the table offline and reverts everything on any key conflict, so the CSV files had to be deduplicated and merged into one file. To save time, only the latest snapshot and the latest 300 journal entries per user were restored, into a new cluster with more storage; the Spark workers used up every available R-family instance in all AZs of the AWS Tokyo region. The journal, snapshot and user tables were restored by 17:22. Checks against a database restored from the last backup showed the recovered data contained every user of the backup, but the user table was badly corrupted, so it was rebuilt from snapshots and journals by server logic instead, and the other tables came from the backup. After sampling users against analytics logs, checking snapshot replay for every user and an internal test, the game reopened at 23:37 on the second day, 30 hours 50 minutes after the start.\n\nThe reopening brought record API traffic to DevPlay, whose AWS RDS database recovered after a failover. Load also built up on the new Kingdom cluster, which used i3 instances; disk write operations appeared to drive CPU load, and at 2:42 all queries failed. The team built a third cluster on more m5d instances, moved the data with CockroachDB's official backup and restore, using settings similar to those of the TPC-C benchmark on CockroachDB support's advice to cut the transfer time, and ended the maintenance at 8:30, 36 hours 29 minutes after it began.","ko":"쿠키런: 킹덤을 출시한 뒤 사용자 수가 계속 늘어 CockroachDB의 스토리지 사용량이 빠르게 늘었다. 첫 주말을 넘긴 월요일에 계산해 보니 이대로면 약 36시간 뒤 디스크가 가득 차 쓰기가 멈출 상황이었다. 팀은 CockroachDB 19.1.11 공식 가이드가 권하는 대로 공간을 미리 차지해 두는 더미 파일인 Ballast 파일을 만드는 스크립트를 실행했다. 클러스터는 로컬 스토리지 디바이스가 두 개인 m5d.8xlarge 24대였고, EC2는 둘 중 하나를 임의로 Primary Mount로 정한다. 설정 문제로 지정 경로가 Primary Mount에 해당한 노드 16대에서 스크립트가 파티션을 덮어썼고, 과반 노드를 잃은 클러스터 전체가 멈췄다. 데이터는 7개 복제본으로 저장하도록 설정돼 있었다.\n\n팀은 쿠키런: 킹덤 개발팀과 논의해, 다른 방법이 있는 한 백업 시점 이후의 데이터를 잃는 백업 복원은 쓰지 않기로 했다. 목표는 장애 직전까지의 모든 데이터를 되살리는 것이었고, 팀은 세 방법을 동시에 진행했다. 운영체제 수준에서 노드를 복원하는 Plan A는 실패했다. 최신 백업에 분석용 로그를 다시 적용해 사용자 상태를 재현하는 Plan C는 핵심 API부터 손댔다. 남은 노드 7대의 SST 파일에서 데이터를 직접 꺼내는 Plan B가 성공했다. 팀은 데이터를 완전히 잃을 확률을 약 3.3%로 계산했다. 또 CockroachDB 소스코드에서 SST 파일 내부를 보는 명령을 찾아 파일 구조를 해독했다. Spark로 약 7.5TB의 SST 파일을 CSV로 추출했고, 중복을 없애 하나의 CSV로 병합했다. 새 클러스터에는 유저마다 가장 최근 스냅샷 1개와 저널 300개만 넣었다. 저널과 스냅샷 테이블은 오후 4시 20분과 4시 57분에 복원됐다. 유저 테이블은 오염이 심해 서버 로직으로 다시 만들었다. 복원한 데이터는 백업으로 만든 데이터베이스와 대조했고, 샘플 유저 검증, 전체 유저 스냅샷 리플레이 검증, 사내망 테스트를 거쳤다. 서비스는 이튿날 23시 37분, 30시간 50분 만에 다시 열었다.\n\n오래 기다린 유저들의 요청이 몰리며 DevPlay 플랫폼 데이터베이스가 연쇄적으로 장애를 일으켰고, AWS RDS Failover로 정상화했다. 새로 구성한 킹덤 클러스터는 i3 계열 인스턴스였다. 지표로 보면 이 클러스터에서 Disk Write Ops 부하가 CPU 부하로 이어졌고, 새벽 2시 42분에는 모든 쿼리가 실패해 2차 긴급 점검에 들어갔다. 팀은 공식 백업과 복원 기능으로 데이터를 옮겨 기존 m5d 인스턴스를 더 많은 대수로 쓰는 세 번째 클러스터를 만들었고, 오전 8시 30분에 총 36시간 29분 만에 점검을 끝냈다. 이후 CockroachDB도 안전장치를 개선했고, 21.2 이상 버전은 Ballast 파일을 자동으로 만든다."},"trigger":{"en":"On the Monday after the launch weekend, storage use was growing with the number of users and the cluster had about 36 hours left before its disks would fill. The team ran a prepared script to create ballast files on every node, as the official guide for CockroachDB 19.1.11 recommends. The ballast file path in the script was set wrong and pointed at a block device, and the command had no check for an existing file at the target path, so it overwrote the partition.","ko":"디스크가 가득 찰 때를 대비해 Ballast 파일을 만드는 스크립트를 실행했는데, 파일 경로가 잘못 설정돼 Block Device를 가리켰다. 이 명령에는 대상 경로에 파일이 이미 있는지 확인하는 안전장치가 없어 파티션을 그대로 덮어썼다."},"severity":"unknown","symptoms":["service-unavailable","data-loss","database-overloaded"],"timeline":{"detected_after":null,"resolved_after":"36h 29m"},"date_kind":"published","detection":{"en":"Metrics and alerts set up in advance fired hundreds of alerts on error rate, database connections and health checks right after the script ran.","ko":"작업 직후 미리 설정해 둔 메트릭으로 알았다. 에러율, 데이터베이스 연결, Health Check 알람이 수백 건 동시에 울렸다. 2차 장애는 새벽 2시 42분 데이터베이스의 모든 쿼리와 요청이 실패하는 것을 관측하고 알았다."},"components":["database"],"root_cause":"human-operation","remediation":[{"text":{"en":"Started emergency maintenance of the game at 16:52","ko":"정상적인 게임 이용이 어렵다고 보고 운영팀에 요청해 16시 52분부터 긴급 점검을 시작했다."}},{"text":{"en":"Wrote Spark code that extracts the contents of CockroachDB SST files to CSV, and modified the CockroachDB CLI to make it more than 10 times faster","ko":"SST 파일의 구조를 해독하고, 장애 다음날 오전 4시쯤 Spark로 SST 파일 내용을 CSV 텍스트 파일로 추출하는 샘플 코드를 작성했다. 예상 처리 시간이 약 24시간으로 너무 길어 CockroachDB CLI 소스코드를 필요한 작업에 맞게 고쳐 10배 이상 빠르게 했다."}},{"text":{"en":"Converted about 7.5TB of SST files from the 7 surviving nodes to CSV","ko":"오전 10시 30분쯤부터 마지막까지 살아남은 노드 7대의 SST 파일 약 7.5TB를 CSV로 변환하며 나오는 에러를 고치는 작업을 반복했다."}},{"text":{"en":"Deduplicated the extracted rows and merged them into one large CSV file that could be imported","ko":"IMPORT INTO는 실행하는 동안 테이블을 오프라인으로 만들고, 키 충돌이 하나라도 나면 전부 Revert한다. 이 제약을 확인한 팀은 이튿날 오후 12시부터 중복을 없애 데이터베이스에 바로 넣을 수 있는 하나의 큰 CSV 파일로 병합하는 Spark 코드를 작성했다."}},{"text":{"en":"Imported the latest snapshot and the latest 300 journal entries per user into a new cluster with more storage","ko":"스토리지가 넉넉한 새 데이터베이스 클러스터에 데이터를 넣기 시작했고, 시간이 너무 오래 걸릴 것으로 보여 유저마다 가장 최근 스냅샷 1개와 가장 최근 저널 300개만 복원하기로 범위를 좁혔다."}},{"text":{"en":"Restored the other tables from the latest backup and rebuilt the user table from snapshots and journals with server logic","ko":"서비스 재개에 쓸 깨끗한 클러스터를 따로 준비해 저널, 스냅샷, 유저 테이블 외의 테이블은 백업에서 복원했다. 저널과 스냅샷 테이블은 전처리한 CSV에서 Import했고, 유저 테이블은 서버 로직으로 다시 만들어 오후 8시에 모든 작업을 마쳤다."}},{"text":{"en":"Checked the restored data against a database restored from backup, against analytics logs for sampled users, and with snapshot replay for every user","ko":"일부 유저를 샘플링해 분석 로그에 기록된 상태와 최종값이 같은지 검증했고, 모든 유저의 스냅샷 리플레이가 정상적으로 동작하는지 검증했다."}},{"text":{"en":"Reopened the game at 23:37 on the second day, after 30 hours 50 minutes","ko":"이튿날 23시 37분, 30시간 50분 만에 서비스를 다시 열었다."}},{"text":{"en":"Failed over the AWS RDS database of the DevPlay platform after the reopening traffic overloaded it","ko":"부하로 장애가 난 DevPlay 플랫폼의 AWS RDS 데이터베이스를 일시적인 순단을 감수하고 Failover해 정상화했다."}},{"text":{"en":"Built a third database cluster on more m5d instances and moved the data with the official backup and restore","ko":"공식 백업과 복원 기능으로 데이터를 옮겨 기존 m5d 인스턴스를 더 많은 대수로 쓰는 세 번째 데이터베이스 클러스터를 구성했다. CockroachDB 서포트 엔지니어들과 논의해 TPC-C 벤치마크에 쓰는 것과 비슷한 설정값을 적용해 데이터를 옮기는 시간을 줄였다."}},{"text":{"en":"Ended the maintenance at 8:30, after 36 hours 29 minutes","ko":"오전 8시 30분, 총 점검 시간 36시간 29분 만에 임시점검을 끝냈다."}}],"action_items":[{"text":{"en":"Require at least two engineers for any work on production infrastructure, however small","ko":"프로덕션 인프라 작업은 아무리 사소해도 반드시 작업자 2명 이상이 참여하도록 규칙을 바꿨다."}},{"text":{"en":"Write the scripts and the scenario for each infrastructure task in advance and have the team review them","ko":"작업에 쓸 스크립트와 작업 시나리오를 엄격하게 작성해 미리 팀 안에 공유하고 리뷰를 받도록 했다."}}],"contributing":["no-safeguard-on-destructive-change","overlapping-failures","retry-storm"],"recurrence_of":null,"symptoms_text":{"en":"Right after a script ran on the CockroachDB nodes behind Cookie Run: Kingdom, hundreds of alerts fired at once for error rate, database connections and health checks, and the game could no longer be played normally. Emergency maintenance started at 16:52.\n\nOn part of the database nodes the filesystem became unstable, writes failed and the cluster isolated those nodes automatically. 16 of the 24 nodes were affected, more than the majority that CockroachDB's Raft consensus needs, so a node-level fault became a failure of the whole cluster. The whole DevOps team spent about 30 minutes reading shell history, CockroachDB logs, kernel logs and the CockroachDB source code before it was clear what had happened.\n\nThe first recovery attempts did not bring the cluster back. The affected partitions were copied to EBS volumes with dd and checked with fsck, which reported so many errors that the repair could not be trusted, and a cluster rebuilt from the fsck output still could not find certain SST files. At 23:02 one of the unaffected nodes went down with a Host Status Failure, leaving 7 healthy nodes. Starting a cluster from those 7 nodes failed because CockroachDB checks the Raft quorum at start-up and the records of the lost nodes kept it from reaching a healthy state; attempts to get around this went on until 5:00 the next morning.\n\nAfter the game reopened, users who had kept checking for the reopening overloaded DevPlay, the Devsisters login platform, and its database failed in a cascade. Later, during the midnight peak, the new Kingdom database cluster showed errors from DB timeouts and some nodes at 100% CPU, and at 2:42 every query and request to the database failed, which forced a second emergency maintenance.","ko":"Ballast 파일을 만드는 스크립트를 실행한 직후 에러율, 데이터베이스 연결, Health Check 알람이 수백 건 한꺼번에 울렸다. 알람 메시지를 읽기도 전에 다음 알람이 쏟아졌고, 정상적인 게임 이용이 어렵다고 본 팀은 16시 52분에 긴급 점검을 시작했다.\n\n커널 작업 화면은 빨간 글씨로 가득했다. 데브옵스 팀 전체가 약 30분 동안 셸 히스토리, CRDB 로그, 커널 로그, CRDB 소스코드를 분석한 뒤에야 무슨 일인지 알았다. 스크립트가 노드 24대 중 16대의 파티션을 덮어써 파일 시스템이 불안정해졌고, 쓰기에 실패한 노드들은 클러스터에서 자동으로 격리됐다. CockroachDB는 Raft 합의 알고리즘으로 동작해 과반 노드가 있어야 하므로, 노드 단위의 장애가 클러스터 전체의 장애로 번졌다.\n\n팀은 먼저 AWS EC2 기술지원에 가상머신 스냅샷이 남아 있는지 물었지만 그런 것은 없었다. 영향받은 머신마다 EBS를 붙이고 dd 명령으로 파티션을 복제한 뒤 fsck를 돌렸지만 오류 항목이 너무 많았다. fsck 결과로 클러스터를 다시 띄워 봐도 특정 SST 파일을 찾을 수 없다는 식의 파일 시스템 오류가 남았다.\n\n복구를 시도하던 23시 02분쯤 영향을 받지 않은 정상 노드 하나가 Host Status Failure로 내려갔다. 이 노드는 EBS 복제도 해 두지 않은 상태여서 남은 노드는 8대에서 7대로 줄었다.\n\n남은 7대만으로 클러스터를 다시 구성하려 했지만, 클러스터가 시작할 때 Raft Quorum을 검증하는 로직이 영향받은 노드들의 기록 때문에 Healthy State로 넘어가지 못했다. 문서화되지 않은 내부 툴까지 써 봤지만 다음날 오전 5시까지 이 단계를 우회하는 방법을 찾지 못했다.\n\n30시간 50분 만에 서비스를 다시 열자, 서버가 언제 열리는지 계속 확인하던 유저들의 요청이 로그인과 인증을 맡는 DevPlay 플랫폼에 몰려 플랫폼 데이터베이스 장애로 이어졌다. 이어 요청이 많은 자정 무렵부터 킹덤 데이터베이스에서 DB Timeout 에러가 관측됐고 일부 노드의 CPU가 100%에 이르렀다. 새벽 2시 42분에는 데이터베이스의 모든 쿼리와 요청이 실패해 2차 긴급 점검에 들어갔다."},"updated_at":"2026-11-14T12:34:21.323683Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0565","org_slug":"devsisters"}