본문 링크

들어가기에 앞서

36시간 장애 속에서 수백만 명이 서비스 오픈만을 기다리고 있고, DB를 만든 회사에서 복구는 불가능하다고 얘기하는 극한의 상황 속에서 CTO가 커리어를 걸고 비트 레벨까지 내려가서 DB를 해킹했던 이야기

해커 문화(Hacker Culture)

  • ‘해킹’의 개념은 MIT에서 시작되었는데, 소프트웨어 시스템이나 하드웨어의 제약사항을 창조적으로 함께 극복해 내는 행위를 뜻함
  • 문제를 해결하거나 시스템의 한계를 극복하기 위해 시스템의 밑바닥까지 파고들고, 그 시스템에 대한 완벽한 이해를 추구
  • 해커 문화와 오픈소스와는 밀접한 연관이 있음
  • 데브시스터즈의 엔지니어링 조직은 해커 문화와 오픈소스를 좋아하며, 사용하는 오픈소스 제품의 코드 레벨까지 다루곤 함

 


문제 발생

개발 환경

  • 메인 데이터베이스로 CockroachDB를 사용
  • CockroachDB는 전통적인 RDBMS처럼 ACID 특성을 가지고 있으며, SQL 기반의 트랜잭션 처리가 완벽하게 작동하는 분산 DB
  • 런칭 전부터 대규모 사용자 유입을 대비하여 데이터베이스는 24대의 노드, 12TB의 스토리지, 7개의 복제본을 두어 만반의 준비를 갖춤

 

오픈 당시 상황

  • 오픈 직후 어마어마한 사용자 유입이 있었고, 한 주가 채 지나기도 전에 스토리지가 약 8TB 정도 차기 시작
  • DevOps 조직은 런칭 첫 주 주말을 장애 없이 넘어가고, 월요일부터 스토리지가 차는 문제를 대응하기 위한 작업에 들어감
  • 데이터베이스 확장 작업 전에 안전장치를 설정하기 위한 작업을 하던 중 의도치 않은 설정 미스로 인해 데이터베이스 노드 중 절반 이상이 다운

 

문제 발생 이유

  • 앞서 CockroachDB는 트랜잭션을 지원한다고 했는데, CockroachDB 클러스터는 기본적으로 가장 높은 수준의 트랜잭션 일관성을 목표로 설정되어 있음
  • 의도하지 않은 설정 이슈로 인해 절반 이상의 노드들이 비일관성을 탐지하고 에러가 발생하면서 클러스터에서 제외되기 시작
  • 이에 따라 데이터베이스는 트랜잭션 일관성을 지켜줄 수 없다고 판단하고 트랜잭션 처리를 중단하는 일종의 보호 모드로 진입
  • 이후 모든 종류의 SQL 쿼리가 처리되지 않게 되고, 게임 서버는 당연히 데이터베이스가 응답이 없으므로, 클라이언트의 모든 요청 처리를 못하게 됨
  • 즉, 서비스 전체 장애가 발생

 


초기 장애 대응

기술 지원 요청

  • DevOps 팀의 최우선 과제는 데이터베이스를 정상 상태로 복원하는 것
  • 장애 시 기술적인 지원을 받을 수 있는 CockroachDB Enterprise를 사용 중이었기 때문에 기술 지원을 요청
  • 상황을 설명하고, 노드의 로그 메시지를 공유하는 등 해당 업체와 함께 트러블슈팅 및 복구 방안을 찾기 위해 노력
  • 그러나 업체 쪽에서는 복구가 불가능하다는 결론
  • 마지막 백업본으로 데이터베이스를 복구하고 서비스를 재개하는 것을 추천

 

서포트 엔지니어의 판단 근거

  • 기술적인 배경에는 CockroachDB의 설계 철학이 있을 것
  • 다수의 노드가 설정 미스로 인하여 클러스터 참여가 불가해진 시점에선 이미 데이터가 일관되게 저장되어 있음을 보장할 수 없음
  • 불완전한 현재 데이터를 복구하는 것 보다는 일관성이 보장된 상태의 백업으로 복구하는 것을 권장한 것으로 판단됨
  • 만약 CRDB 기반으로 은행 서비스를 개발했다면, 실제로 데이터베이스 복구 작업은 불가하다고 보는 게 맞고, 백업에서 복원하는 게 가장 좋은 선택일지도 모름

 

쿠킹덤의 상황

  • 그러나 쿠킹덤의 경우, 서버의 구조에 Snapshot과 Journal을 사용해 데이터를 커밋 하는 Event Sourcing 기반의 아키텍처를 사용했음
  • 따라서 데이터베이스에 있는 Snapshot, Journal 데이터를 가져올 수만 있다면 확률적으로 거의 완벽한 데이터 복구가 가능하다고 판단
  • 이에 따라 CockroachDB에 저장되어 있는 원시 데이터에서 테이블에 저장된 row들을 복구하는 방법에 대해 문의 → 그런 방법은 존재하지 않는다는 답변
  • 그러나 분명히 데이터는 스토리지에 저장되어 있고, 이를 끄집어낼 수 있는 방법이 있을 거라고 생각하고 이 점을 고민하기 시작함
  • 실제 서비스의 장애 상황에서 이런 접근을 하기 쉽지는 않았으나, DB복구가 불가능할 경우를 대비한 로그 기반의 복구를 플랜B로 따로 진행하고 있었기에 가능했음

 


복구 데이터 탐색

아키텍쳐에 대한 사전 지식

  • CRDB에 저장되어 있는 데이터를 끄집어낼 수 있는 방법을 리서치하기 시작
  • 런칭 전에 CockroachDB 스터디를 진행했기 때문에 CRDB의 아키텍처에 대해서 일부 파악하고 있던 지식들이 있었음
  • 하위 스토리지 레이어는 Pebble로 구성된 Key-Value 스토리지를 사용한다는 사실, 그 위에 Raft 기반의 Consensus Algorithm, 이를 통해 ACID 및 트랜잭션 구현을 했고, 그 위에 SQL 레이어를 구현했다는 것

 

스토리지 살펴보기

  • 실제 노드에 물리적으로 저장된 파일들을 파보기 시작(sst 확장자의 파일들)
    • CockroachDB에서 사용하는 Pebble: Facebook에서 만든 RocksDB의 후계자
      RocksDB: Google에서 만든 LevelDB의 후계자
    • sst 파일은 LevelDB가 스토리지에 데이터를 물리적으로 저장할 때 사용하는 확장자임
  • Pebble의 sst 파일이 RocksDB, LevelDB의 sst 파일과 호환이 될지 살펴봄

 

원하는 데이터 발굴

  • 공식 문서에 따르면, Pebble은 RocksDB와 on-disk format 호환을 목표로 한다고 되어있음
  • 하지만 RocksDB의 경우 LevelDB와 API 레벨에서의 호환성만 얘기하지, on-disk format 에 대한 이야기는 찾아볼 수 없었음
  • 이에 따라 on-disk format이 동일한 RocksDB 의 구조를 파악하기 시작
  • 조금 다르긴 하지만 기본적인 구성은 LevelDB와 유사하며, 확장한 형태로 보였음
  • 실제 온전한 CRDB sst 파일들을 받아서 분석해 보기 시작
  • hexdump 명령어로 원하던 데이터를 발견

 


데이터 파싱

CRDB 구조 파악

  • sst 파일의 뒷부분의 metadata를 읽어야 data block들을 읽을 수 있음
  • 다만 실제 data block들에 들어있는 CRDB의 row 데이터를 저장하는 방식도 파악할 필요를 느낌
  • CRDB 문서를 통해 Column Family라는 개념으로 K-V Store 저장되는 것을 확인
  • 구체적인 저장 방식을 확인하기 위해 CRDB 소스코드 레포지토리 내에 있는 Design Document 들을 읽어봄

 

코드레벨 분석

  • 소스코드를 탐색하다보니 실제 저장에 사용되는 Protocol Buffer 파일을 발견
  • 주석에 따르면 Value 값이 <4-byte-checksum><1-byte-tag><encoded-data> 형태로 저장되고 있음
  • 스토리지에 저장된 파일을 파싱하기 위한 코드도 발견
  • <1-byte-tag>를 읽고 case 문으로 Value Type 별로 별도의 파싱 루틴을 타서 예쁘게 출력을 하는 구조를 확인

 

가능성 검증

  • 실제 데이터가 들어있는 sst 파일을 하나 hexdump로 열어봄
  • 문서를 참고하여 데이터 타입, 길이 등을 지나면 실제로 저장된 데이터 값들이 보임(53~)
  • 이론적으로 파싱이 가능한 것에서, 실제로 손 파싱을 통해 데이터를 파싱해올 수 있음을 증명
  • 이제 7TB의 데이터를 전부 꺼내오는 방법만 고민하면 됨

 


데이터 저장

우선순위 판단

  • 빠르게(장애 상황이기 때문), 그리고 정확하게(사용자의 데이터이기 때문), 좋은 성능으로(7TB) 구현해야 했음
  • 우선순위를 따져보면, 처리 성능보다는 빠르게 구현 가능하면서 정확해야 함
  • 처리 성능은 데브시스터즈 데이터 분산처리 역량과 AWS의 클라우드 컴퓨팅 자원을 믿고 돈으로 성능을 살 수 있다고 판단

 

해결 방안 탐색

  • 결과적으로 채택한 것은 소스코드를 읽어보면서 발견한 PrettyPrint 함수
  • CLI 툴에서 디버깅을 위한 기능에서 사용되는 기술로, 결국 바이너리 sst 파일을 파싱해서 엔지니어가 읽을 수 있는 형태로 터미널에 출력해 주는 함수
  • 이를 변경해서 CSV로 출력하기로 함
  • 가칭 crdb2csv 프로그래밍 작업에 착수

 

CSV 분산 처리

  • 어떻게 하면 7TB의 데이터를 분산처리하여 csv 파일을 만들 수 있을지 고민
  • PySpark 샘플 코드를 작성해 보면서 crdb2csv 프로그램이 왔을 때를 대비하여 분산처리를 준비
  • csv 파일은 텍스트 포맷이고, stdout 출력물을 받으니 처리시간이 오래 걸리는 것은 어쩔 수 없음
  • AWS 클라우드에서 컴퓨팅 자원을 끌어모을 수 있을 만큼 끌어모음
  • 결과적으로 어떻게든 확보한 노드들로 7TB의 데이터를 CSV 파일로 변환하는 데만 4시간이 걸림
  • crdb2csv 프로그램의 오류를 발견하여 이 4시간이 소요되는 작업도 여러 번 반복했음

 

CSV 파일 검증

  • 변환한 파일에 모든 사용자 데이터가 담겨있는지 검증하는 작업도 꽤 오래 걸림
  • 데이터베이스가 사용하는 MVCC 구조상 필요 없는 사용자의 데이터가 다수 있었기 때문에 이를 제거하고, 최신 값을 뽑아냄
  • 이렇게 뽑아낸 데이터를 백업본, 분석 로그와 교차 검증을 하면서 빠진 데이터가 없는지 체크함
  • 결과적으로 뽑아낸 파일에 모든 사용자 데이터가 정확하게 담겨있음을 확인

 


서비스 복구

새로운 클러스터 준비

  • 인프라 조직에서는 새로운 CockroachDB 클러스터를 준비하고 있었음
  • AWS 노드들을 다 끌어다 쓴 탓에, 해당 클러스터를 셋업하는 것도 힘들었음
  • 결과적으로 클러스터 셋업이 완료되고, 확인이 완료된 CSV 파일을 클러스터에 적재함
  • 불러올 데이터가 크다 보니 예상외로 이것도 상당한 시간이 소요됨
  • 이렇게 다시 정상 작동하는 데이터베이스를 만들었고, 이를 전사적으로 테스트를 거쳐서 36시간 만에 서비스를 다시 오픈!

 


회고

  • 이러한 서비스 복구 방식은 전혀 일반적인 방식은 아니라고 생각했음
  • 데이터베이스 업체에서 복구 불가 판정을 내렸으면, 이미 그 시점부터 백업본으로 서비스를 복구하는 소위 ‘백섭'을 하는 게 일반적이었을 것
  • 시간은 다소 오래 걸렸을지라도 이런 방식으로 장애를 복구한 것에는 이유가 있음

 

개발 문화

  • 해커 문화를 가지고 있기 때문에 엔지니어들이 로우 레벨로 뛰어드는 것에 익숙했음
  • 데이터베이스 레벨부터 커널, VM 레벨까지 다양한 경험이 있는 엔지니어들이 있었음
  • 상호 지식공유를 통해 데이터베이스의 다양한 추상화 레벨에 대한 전방위적인 이해가 가능했음
  • 남의 소스코드를 읽고 고치는 것에 익숙한 조직임
  • 오픈소스 프로젝트들을 매일 이용하고, 문제가 있으면 소스코드를 읽고, 필요하다면 직접 고치면서 기여하기 때문에 가능했음

 

조직 구조

  • 데브시스터즈 산하 CTO 조직인 진저랩은 데이터와 인프라 관련 팀들이 한 조직에 속해있음
  • 일반적으로 분산처리 플랫폼은 지표 등의 데이터 분석에 주로 사용되는데, 데이터와 인프라가 한 조직에 있다 보니 분산처리 역량으로 인프라 장애를 극복하는 특별한 시너지가 가능했음

 

다양한 경험

  • 확장성 있는 인프라를 다루는 경험, 대용량의 데이터를 분산처리하는 경험이 풍부했음
  • 장애 대응 경험이 풍부했기 때문에 Plan A/B/C를 동시에 가동하는 등 여러가지 복구 전략을 함께 진행했고, 실제로 두 가지 복구 전략을 혼합하여 완벽하게 장애 복구를 할 수 있었음
  • 조를 나누어서 여러 가지 복구 전략을 동시 실행했기 때문에, 내가 실패하더라도 플랜 B를 실행하는 동료를 믿고 과감하게 용기를 낼 수 있었음

 


국내 반응

부정적

  • 서비스 측면에서, 과연 일부 사용자의 경험을 훼손하고 대다수를 지키는 롤백보다 36시간에 걸쳐서 모든 사용자 정보를 복구하는것이 비즈니스적으로 옳은 판단일까?
  • 만약에 문제를 해결하지 못했으면 시간은 시간대로 낭비하고 백섭은 백섭대로 해서 원망을 들었을 것
  • 해당 프로젝트에서는 CockroachDB를 사용하지 말았어야 했다는 의견이 다수

 

긍정적

  • 요즘 게임에서 (특히 랜덤 뽑기류가 있는 게임에서) 롤백은 진짜 최악의 경우에만 쓸 수 있는 방법으로, 주로 보상을 크게 주는 편이기 때문에 옳은 판단이었다
  • 게임이다보니 36시간 전으로 롤백하면 캐쉬 관련으로 금전적 손해가 많았을 수도 있었을 것
  • 적절한 기술 기반과 준비된 아키텍처로 어떻게든 백섭이라는 "최악의 사태"를 회피한건 분명한 엔지니어링 성과로 평가받아야 함

 

부정 의견에 대한 반박

  • 36시간 다운타임을 기대매출로 환산했을 때와, 롤백 후 보상지급액을 비교하고 결정한 사안이었을 것. 문제 해결을 중시하는 개발자 문화가 강한 조직이라는 점도 어느정도 영향이 있었을 것 같다
  • DB를 정할 때 저만한 프로젝트에서 어련히 벤치마크 안했을까. 그저 익숙한 기술을 썼으면 안터졌을거라는 나이브한 주장은 너무 반엔지니어링적인 사고

+ Recent posts