CTO가 커리어를 걸고 비트 레벨까지 내려가서 DB를 해킹했던 이야기

홍성진

이 글과 관련된 다른 글 보기


소프트웨어 엔지니어들은 수많은 추상화 속에서 살아갑니다. 하위 레벨에서 제공하는 추상화는 공기처럼 당연한 것으로 여겨지고, 그 위에 무엇을 어떻게 쌓아 올릴지 고민하는 게 소프트웨어 엔지니어링이라 할 수 있습니다. 오늘은 이렇게 당연하게 여겼던 추상화를 하나하나 파고들어간 이야기를 소개해 볼까 합니다. 수십 명의 개발자들이 만들었던 게임. 36시간 장애 속에서 수백만 명이 서비스 오픈만을 기다리고 있고, DB를 만든 회사에서 복구는 불가능하다고 얘기하는 극한의 상황 속에서 CTO가 커리어를 걸고 비트 레벨까지 내려가서 DB를 해킹했던 이야기를 소개합니다.

이야기를 시작하기에 앞서 해커 문화(Hacker Culture)를 알고 갑시다. 대중적으로 ‘해킹’이라는 단어는 시스템을 불법적으로 침입하는 행위로 알려져 있습니다. 원래 ‘해킹’의 개념은 MIT에서 시작되었는데, 소프트웨어 시스템이나 하드웨어의 제약사항을 창조적으로 함께 극복해 내는 행위를 뜻합니다. 문제를 해결하거나 시스템의 한계를 극복하기 위해 시스템의 밑바닥까지 파고들고, 그 시스템에 대한 완벽한 이해를 추구합니다. 이러한 해커 문화를 이끌어간 리더들로는 GNU 창시자 RMS, C 언어를 개발한 Dennis Ritchie, Go 언어를 개발한 Ken Thompson, 리눅스를 개발한 Linus Torvalds 등이 있습니다. 보시면 아시겠지만 해커 문화와 오픈소스와는 밀접한 연관이 있습니다.

데브시스터즈의 엔지니어링 조직은 뛰어난 엔지니어링을 지향함과 동시에 해커 문화와 오픈소스를 좋아합니다. 사용하는 오픈소스 제품의 코드 레벨까지 뛰어드는 걸 주저하지 않습니다. 여기서 소개되는 이야기는 이러한 문화가 얼마나 극한까지 갈 수 있는지 보여주며, 극한의 데이터베이스 해킹을 통해 장애를 극복하는 기술적 이야기를 다룹니다.

이야기의 시작은 데브시스터즈 역사상 가장 길었던 쿠키런: 킹덤의 36시간 장애에서 시작합니다. 쿠키런: 킹덤은 메인 데이터베이스로 CockroachDB를 사용합니다. CockroachDB는 우리가 익숙한 전통적인 RDBMS처럼 ACID 특성을 가지고 있으며, SQL 기반의 트랜잭션 처리가 완벽하게 작동하는 분산 데이터베이스입니다. 런칭 전부터 대규모 사용자 유입을 대비하여 데이터베이스는 24대의 노드, 12TB의 스토리지, 7개의 복제본을 두는 설정으로 만반의 준비를 했는데요. 실제로 오픈 직후 어마어마한 사용자 유입이 있었고, 한 주가 채 지나기도 전에 스토리지가 약 8TB 정도로 꽉꽉 차기 시작합니다.

01 crdb capacity usage

일반적으로 공휴일이 가장 큰 허들인데요. DevOps 조직인 인프라셀은 런칭 첫 주 주말을 장애 없이 넘어갑니다. 이후 월요일부터 스토리지가 차는 문제를 대응하기 위한 작업에 들어갑니다. 여기서 문제의 발단이 시작됩니다. 데이터베이스 확장 작업 전에 안전장치를 설정하기 위한 작업을 하던 중 의도치 않은 설정 미스로 인해 데이터베이스 노드 중 절반 이상이 다운되게 됩니다.

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

02 crk community maintenance notice

장애를 인지하자마자 저를 포함해 회사의 모든 가용인력이 장애 대응을 위한 비상 대응에 들어갑니다. 비상 대응에 들어가면서 두 가지 계획을 병행해서 운영하게 됩니다(상세한 장애 대응 기록은 별도의 기술블로그 글을 참고하시면 좋습니다.). 그 중 DevOps 팀의 최우선 과제는 데이터베이스를 정상 상태로 복원하는 것이었습니다.

쿠키런: 킹덤에는 장애 시 기술적인 지원을 받을 수 있는 CockroachDB Enterprise를 사용 중이었는데요. 신속히 기술 지원 요청을 보냈고, 온 콜 대기 중이던 서포트 엔지니어와 연락을 하게 됩니다. 상황을 설명하고, 노드의 로그 메시지를 공유하는 등 해당 업체와 함께 트러블슈팅 및 복구 방안을 찾기 위한 작업을 하게 됩니다. 하지만 결론적으로 몇 시간 뒤 상황에 대한 진단이 완료되고 나니, 업체 쪽에서는 복구가 불가능하다는 결론이 납니다. 마지막 백업본으로 데이터베이스를 복구하고 서비스를 재개하는 것을 추천합니다.

서포트 엔지니어가 이러한 추천을 한 기술적인 배경에는 CockroachDB의 설계 철학이 있다고 생각됩니다. CRDB는 Consensus Algorithm을 기반으로 Majority Node에 커밋 된 데이터를 통하여 일관성을 확보하기 때문인데요. 다수의 노드가 설정 미스로 인하여 클러스터 참여가 불가해진 시점에선 이미 데이터가 일관되게 저장되어 있음을 보장할 수 없기 때문에 불완전한 현재 데이터를 복구 하는 것 보다는 일관성이 보장된 상태의 백업으로 복구하는 것을 권장한 것으로 판단됩니다. 만약 CRDB 기반으로 은행 서비스를 개발했다면, 실제로 데이터베이스 복구 작업은 불가하다고 보는 게 맞고, 백업에서 복원하는 게 가장 좋은 선택일지도 모르겠습니다.

하지만 우리의 경우는 상황이 많이 달랐는데요. 쿠키런: 킹덤 서버의 구조는 Snapshot과 Journal을 사용해 데이터를 커밋 하는 Event Sourcing 기반의 아키텍처를 사용합니다.[1] 따라서 데이터베이스에 있는 Snapshot, Journal 데이터를 가져올 수만 있다면 확률적으로 거의 완벽한 데이터 복구가 가능하다고 판단했습니다. 이에 따라 CockroachDB에 저장되어 있는 원시 데이터에서 테이블에 저장된 row들을 복구하는 방법에 대해 문의하였는데요. 서포트 엔지니어는 그런 마땅한 방법은 존재하지 않는다는 답변을 했습니다. 답변을 듣고 좌절하긴 했지만 (…) 분명히 데이터는 스토리지에 저장되어 있고, 이를 끄집어낼 수 있는 방법이 있을 거라고 생각했습니다. 이론적으로 가능하면, 실제로도 가능하니까요. [2]

저와 함께 몇몇 엔지니어들은 CRDB에 저장되어 있는 데이터를 끄집어낼 수 있는 방법을 리서치하기 시작합니다. 우선 CRDB의 아키텍처에 대해서 일부 파악하고 있던 지식들이 있었는데요. 하위 스토리지 레이어는 Pebble로 구성된 Key-Value 스토리지를 사용한다는 사실, 그 위에 Raft 기반의 Consensus Algorithm, 이를 통해 ACID 및 트랜잭션 구현을 했고, 그 위에 SQL 레이어를 구현했다는 것을 알고 있었습니다. 런칭 전에 CockroachDB 스터디를 함께 했었거든요.

03 cockroach labs architecture overview
Cockroach Labs Architecture Overview

우리는 저장되어 있는 스토리지에서 데이터를 뽑아낼 수 있는 방법이 없을까 고민하기 시작했습니다. 실제 노드에 물리적으로 저장된 파일들을 파보기 시작합니다. sst 확장자의 파일들이 보입니다.

[ec2-user@ip-172-21-76-209 /]$ cd /mnt/nvme1n1/
[ec2-user@ip-172-21-76-209 nvme1n1]$ ls -al
total 1749736
drwxrwsr-x 5 root root    12288 Jan 25 08:09 .
drwxr-xr-x 3 root root       21 Dec 22 15:15 ..
-rw-r----- 1 root root  4179539 Dec 24 09:35 001335.sst
-rw-r----- 1 root root  4194844 Dec 24 09:35 001336.sst
-rw-r----- 1 root root  1412741 Dec 24 09:58 001364.sst
-rw-r----- 1 root root  8405018 Dec 25 14:11 003425.sst
-rw-r----- 1 root root  8337500 Dec 25 14:11 003426.sst

여기서 sst 파일은 무엇일까요? CockroachDB에서 사용하는 Pebble은 Facebook에서 만든 RocksDB의 후계자이고, RocksDB는 Google에서 만든 LevelDB의 후계자입니다.[3] sst 파일은 LevelDB가 스토리지에 데이터를 물리적으로 저장할 때 사용하는 확장자입니다. Pebble의 sst 파일이 RocksDB, LevelDB의 sst 파일과 호환이 될지 살펴봅니다.

https://www.cockroachlabs.com/docs/stable/architecture/storage-layer.html

CockroachDB uses the Pebble storage engine. Pebble is intended to be bidirectionally compatible with the RocksDB on-disk format, but differs in that it is written in Go and implements a subset of RocksDB's large feature set.

우선 Pebble은 RocksDB와 on-disk format 호환을 목표로 한다고 되어있습니다. 하지만 RocksDB의 경우 LevelDB와 API 레벨에서의 호환성만 얘기하지, on-disk format 에 대한 이야기는 찾아볼 수 없었습니다.

https://github.com/facebook/rocksdb/wiki

It was developed at Facebook based on LevelDB and provides backwards-compatible support for LevelDB APIs.

이에 따라 on-disk format이 동일한 RocksDB 의 구조를 파악하기 시작합니다. 조금 다르긴 하지만 기본적인 구성은 LevelDB와 유사하며, 확장한 형태로 보였습니다.

https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format

<beginning_of_file>
[data block 1]
[data block 2]
...
[data block N]
[meta block 1: filter block]                  (see section: "filter" Meta Block)
[meta block 2: index block]
[meta block 3: compression dictionary block]  (see section: "compression dictionary" Meta Block)
[meta block 4: range deletion block]          (see section: "range deletion" Meta Block)
[meta block 5: stats block]                   (see section: "properties" Meta Block)
...
[meta block K: future extended block]  (we may add more meta blocks in the future)
[metaindex block]
[Footer]                               (fixed size; starts at file_size - sizeof(Footer))
<end_of_file>

실제 온전한 CRDB sst 파일들을 받아서 분석해 보기 시작합니다. hexdump 명령으로 보니까 내가 원하던 데이터가 들어있었습니다.

04 hexdump of sst file

파일을 어떻게 파싱해서 우리가 원하는 데이터를 읽어올지 고민해 봅니다. sst 파일의 뒷부분에 metadata를 읽어야 data block들을 읽을 수 있습니다. 다만 실제 data block 들에 들어있는 CRDB의 row 데이터를 저장하는 방식도 파악이 필요해 보였습니다. 얘네들은 어떻게 구성되어 있는 걸까요?

05 crdb column family documentation
Cockroach Labs Column Families

CRDB 문서를 읽어보니 Column Family라는 개념으로 K-V Store 저장되는 것을 확인했습니다. 구체적으로 어떻게 저장되는 걸까요? CRDB 소스코드 레포지토리 내에 있는 Design Document 들을 읽어보기 시작했습니다.

/<tableID>/<indexID>/<primaryKeyColumns...>/<columnID>

소스코드를 탐색하다보니 실제로 저장에 사용되는 Protocol Buffer 파일을 발견합니다.

https://github.com/cockroachdb/cockroach/blob/v21.1.19/pkg/roachpb/data.proto

// Value specifies the value at a key. Multiple values at the same key are
// supported based on timestamp. The data stored within a value is typed
// (ValueType) and custom encoded into the raw_bytes field. A custom encoding
// is used instead of separate proto fields to avoid proto overhead and to
// avoid unnecessary encoding and decoding as the value gets read from disk and
// passed through the network. The format is:
//
//   <4-byte-checksum><1-byte-tag><encoded-data>

주석을 읽어보니 Value 값이 <4-byte-checksum><1-byte-tag><encoded-data> 형태로 저장되고 있습니다. 스토리지에 저장된 파일을 직접 파싱해보고 싶어집니다. 파싱하는 코드도 발견합니다.

func (v Value) PrettyPrint() string {
  // ...
 var buf bytes.Buffer
 t := v.GetTag()
  // ...

 switch t {
 case ValueType_TUPLE:
  b := v.dataBytes()
  var colID uint32
  for i := 0; len(b) > 0; i++ {
      // ...
   _, _, colIDDiff, typ, err := encoding.DecodeValueTag(b)
   // ...
   b, s, err = encoding.PrettyPrintValueEncoded(b)
   // ...
   fmt.Fprintf(&buf, "%d:%d:%s/%s", colIDDiff, colID, typ, s)
  }
  // ...

코드를 쭉 읽어보면 앞서 주석에서 읽었던 순서대로 (checksum 은 생략하고) <1-byte-tag>를 읽고 case 문으로 Value Type 별로 별도의 파싱 루틴을 타서 예쁘게 출력을 하는 구조를 볼 수 있습니다.

06 hexdump one byte tag

실제 데이터가 들어있는 sst 파일을 하나 hexdump로 열어봅니다. 우리가 관심 있는 데이터는 보통 string 형태로 저장된게 보입니다. 그 부근으로 이동하여 <4-byte-checksum><1-byte-tag><encoded-data> 형태의 바이너리 패턴이 보이는지 확인해 봅니다. Checksum을 건너뛰고, 1-byte-tag 섹션을 체크해 봅니다. 값이 0x0a, 10진수로 10입니다. ValueType_TUPLE이네요. Tuple 은 내부 값을 어떻게 저장하고 있을까요? 주석을 확인해 보니 다음과 같이 설명합니다.

TUPLE represents a DTuple, encoded as repeated pairs of varint field number followed by a value encoded Datum.

DTuple 이 뭔지는 모르겠으나 장애 상황이니 일단 넘어갑니다. 인코딩을 설명하는데 varint 타입으로 field number를 저장하고, 그 이후에는 다시 Value 인코딩이라는 얘기입니다. 인코딩된 데이터를 확인해 봅니다. 위 경우는 첫 번째 아이템은 필드 번호 0x34이고 그다음에 다시 태그 값 0x08이 나옵니다. 문서를 보면 DELIMITED_BYTES라네요. DELIMITED가 붙어있지만 일단 BYTES라고 가정하고 파싱을 해봅시다. 그다음 바이트들을 체크해 봅니다. 0x01 0x12는 뭔지 잘 모르겠지만 그다음 0x30 은 왠지 Byte의 길이를 나타내는 값이 나오는 것 같네요. 실제로 10진수로 48인데, 뒤에 나오는 아스키 값들을 카운팅 해보니 길이가 48이 맞습니다. Protocol Buffer Encoding 문서를 보니 다음과 같이 설명합니다.

A wire type of 2 (length-delimited) means that the value is a varint encoded length followed by the specified number of bytes of data.

Protocol Buffer의 varint 타입은 최대한 많은 숫자들을 압축해서 저장하기 위한 protocol buffer에서 고안된 기법입니다. 자세한 내용은 Protocol Buffer Encoding 문서의 Base 128 Varints를 참고합니다. 필자의 경우도 이게 뭔지는 대략 알고 있었지만 기억이 확실하지 않게 때문에 문서를 확인하면서 파싱을 해봅니다. 파싱 중인 예시를 varint 기법에 맞게 파싱을 해보니 여전히 48이 나옵니다. Wire type 2 (LEN) 파싱 관련 문서 Length-Delimited Records의 예시를 보니 익숙한 패턴이 보입니다.

12 07 [74 65 73 74 69 6e 67]

즉, 0x12는 Wire type 2 (LEN) 을 알려주는 태그이며, 그다음 나오는 값이 varint로 인코딩된 길이인 것이었습니다. 우리의 실제 데이터의 경우에는 0x30, 즉 48이었던 것이죠. 여전히 0x01의 의미를 모르겠는데, 일단 넘어갑니다.

길이 이후에는 우리가 그토록 꺼내고 싶었던 저장된 데이터 값들이 보입니다. 희망이 생겼습니다. 이론적으로 파싱이 가능한 것에서, 실제로 손 파싱을 통해 데이터를 파싱해올 수 있음을 증명해냈습니다. 이제 데이터가 어디에 어떻게 저장되어 있는지 알고 있습니다. 7TB의 데이터를 전부 꺼내오는 방법을 고민하면 됩니다.

장애 상황이기 때문에 빠르게 구현해야 했습니다. 사용자의 데이터이기 때문에 정확하기도 해야 합니다. 저장된 데이터가 약 7TB 정도 됩니다, 따라서 처리 속도도 좋아야 합니다. 가장 어렵다는 세 가지를 만족해야 했습니다: 빨리 만들고, 오류 없어야 하고, 성능도 좋아야 했던 것이죠. 우선순위를 따져봅니다. 우선 빠르게 구현 가능하면서 정확해야 합니다. 처리 성능은 데브시스터즈 데이터플랫폼셀의 데이터 분산처리 역량과 AWS의 클라우드 컴퓨팅 자원을 믿고 돈으로 성능을 살 수 있다고 판단했습니다.

빠르고 정확하게 구현하기 위한 방법을 팀과 치열하게 고민했습니다. 다양한 아이디어가 나왔지만, 결과적으로 채택한 것은 소스코드를 읽어보면서 발견한 PrettyPrint 함수입니다. 이게 어디에서 사용되나 보니까 CLI 툴에서 디버깅을 위한 기능에서 사용됩니다. 결국 바이너리 sst 파일을 파싱해서 엔지니어가 읽을 수 있는 형태로 터미널에 출력해 주는 함수입니다. 그렇다면 이걸 변경해서 csv를 출력하게 하면 어떨까요?

CockroachDB CLI 소스코드를 수정해 줄 사람을 찾아봅니다. 데브시스터즈에서 Go 언어에 가장 뛰어나고, 스트레스 상황 속에서도 손이 빠르고, 정확한 사람을 찾아야 합니다. 인프라셀 셀장님을 선정합니다. 스펙을 논의하여 명확히 하고 가칭 crdb2csv 프로그래밍 작업에 착수합니다.

07 crdb example csv

필자는 그 사이에 7TB의 데이터를 어떻게 하면 분산처리하여 csv 파일을 만들 수 있을지 고민해 봅니다. 데이터 분산처리에 경험이 많은 데이터플랫폼셀 엔지니어 분들과 해당 방법에 대한 논의를 시작합니다. PySpark 샘플 코드를 작성해 보면서 crdb2csv 프로그램이 왔을 때를 대비하여 분산처리 준비를 해둡니다.

프로토타입이 완성되어 이를 PySpark 코드를 통해 7TB의 sst 파일들에 대해서 돌려봅니다. 에러가 발생합니다. 에러 메시지를 공유하여 crdb2csv 소스코드를 디버깅하고 고칩니다. 이러한 작업을 여러 번 수행하고 나니 점점 완성도가 높아집니다.

def download_then_parse_sst(row):
    '''
    파라미터로 입력된 S3 버킷에 있는 sst 파일을 다운받아 내용물을 crdb2csv로 변환하여 문자열 반환
    '''
    key_name = row
    with NamedTemporaryFile() as file:
        Key(Bucket(connect_s3(), INPUT_BUCKET_NAME), key_name).get_contents_to_filename(file.name)
        run = subprocess.Popen([CLI, "crdb2csv", file.name], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)

        while run.poll() is None:
            line = run.stdout.readline()
            yield Row(
                key_name=key_name,
                stdout=line.decode('utf-8').strip(),
            )
        output = run.communicate()[0]
        for line in output.decode('utf-8').split("\n"):
            yield Row(
                key_name=key_name,
                stdout=line,
            )

처리시간이 오래 걸립니다. 아무래도 csv 파일은 텍스트 포맷이고, stdout 출력물을 받으니 느릴 수밖에 없습니다. 예상되던 부분입니다. AWS 클라우드에서 컴퓨팅 자원을 끌어모을 수 있을 만큼 끌어모읍니다.

08 aws console screenshot

기록을 확인하니 Spark 클러스터에만 r5.8xlarge 350대를 사용했습니다. CPU 11,200코어 / 메모리 89,600GB 정도 되는 사양입니다. 이마저도 AWS의 도쿄 리전 캐퍼시티를 다 사용해버려서 350대를 운용하는 데에 큰 어려움을 겪었습니다. 결과적으로 어떻게든 확보한 노드들로 7TB의 데이터를 CSV 파일로 변환하는 데만 4시간이 걸렸습니다. crdb2csv 프로그램의 오류를 발견하여 이 4시간이 소요되는 작업도 여러 번 반복합니다.

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

이것들을 확인하는 중에 인프라 조직에서는 새로운 CockroachDB 클러스터를 준비하고 있었는데요. AWS 노드들을 다 끌어다 쓴 탓에, 해당 클러스터를 셋업하는 것도 여간 힘든 일이 아니었습니다. 결과적으로 클러스터 셋업이 완료되고, 확인이 완료된 CSV 파일을 클러스터에 적재하였습니다. 불러올 데이터가 크다 보니 예상외로 이것도 상당한 시간이 소요되었습니다.

이렇게 다시 정상 작동하는 데이터베이스를 만들었고, 이를 전사적으로 테스트를 거쳐서 36시간 만에 서비스를 다시 오픈하게 되었습니다(사실 오랜 시간 기다려주신 고객분들의 열렬한 성원에 힘입어 추가적인 장애들이 발생했는데요. 이에 대해서는 다음 기회에 다뤄보도록 하겠습니다.).

일련의 장애 복구 과정을 회고해 볼 때, 이러한 서비스 복구 방식은 전혀 일반적인 방식은 아니라는 생각이 들었습니다. 데이터베이스 업체에서 복구 불가 판정을 내렸으면, 이미 그 시점부터 백업본으로 서비스를 복구하는 소위 ‘백섭'을 하는 게 일반적이었을 겁니다. 시간은 다소 오래 걸렸을지라도 데브시스터즈 엔지니어들이 이런 방식으로 장애를 복구한 것에는 다음과 같은 이유가 있다고 생각합니다:

  • 고객에게 최고의 경험을 선사하는 게 우리의 미션이고, 이를 진심으로 실천하기 위해 노력했습니다. 그 누구도 낙담하고 포기하지 않았습니다.
  • 해커 문화를 가지고 있기 때문에 엔지니어들이 로우 레벨로 뛰어드는 것에 익숙했습니다. 데이터베이스 레벨부터 커널, VM 레벨까지 다양한 경험이 있는 엔지니어들이 있었습니다. 상호 지식공유를 통해 데이터베이스의 다양한 추상화 레벨에 대한 전방위적인 이해가 가능했습니다.
  • 남의 소스코드를 읽고 고치는 것에 익숙한 조직입니다. 오픈소스 프로젝트들을 매일 이용하고, 문제가 있으면 소스코드를 읽고, 필요하다면 직접 고치면서 기여하기 때문에 가능했습니다.
  • 데브시스터즈 산하 CTO 조직인 진저랩은 데이터와 인프라 관련 팀들이 한 조직에 속해있습니다. 일반적으로 분산처리 플랫폼은 지표 등의 데이터 분석에 주로 사용되는데요. 데이터와 인프라가 한 조직에 있다 보니 분산처리 역량으로 인프라 장애를 극복하는 특별한 시너지가 가능했습니다.
  • 확장성 있는 인프라를 다루는 경험, 대용량의 데이터를 분산처리하는 경험이 풍부했습니다. 물론 7TB 단위의 데이터베이스 바이너리 데이터를 이렇게 극한 상황에서 다루는 건 처음이었고, 이를 통해 더 많이 성장했습니다.
  • 장애 대응 경험이 풍부했기 때문에 Plan A/B/C를 동시에 가동하는 등 여러가지 복구 전략을 함께 진행했고, 실제로 두 가지 복구 전략을 혼합하여 완벽하게 장애 복구를 할 수 있었습니다.
  • 조를 나누어서 여러 가지 복구 전략을 동시 실행했기 때문에, 내가 실패하더라도 플랜 B를 실행하는 동료를 믿고 과감하게 앞으로 전진할 수 있는 용기를 낼 수 있었습니다.

엔지니어로서 커리어의 최대 위기가 왔었고, 팀 분들과 함께 쌓아왔던 우리의 소중한 문화, 핵심가치, 경험 덕분에 이를 극복해낼 수 있었던 것 같습니다. 다시는 경험하고 싶지 않은 장애지만, 또 한편으로는 데브시스터즈의 팀워크가 가장 빛났던 순간이었던 것 같습니다. 뛰어난 동료분들과 최악의 장애를 함께 극복하는 경험은 평생 절대 잊을 수 없는 경험이었습니다. 저는 우리가 이 장애로 또 한 번 성장했음을 느낍니다.

지금까지의 글이 흥미로우셨다면, 이러한 문화와 핵심가치가 공감이 되신다면, 저희와 함께 일해보시는 건 어떨까요? 채용의 문은 활짝 열려있습니다. 지향점이 같은 훌륭한 미래의 동료분들을 모십니다. 읽어주셔서 감사합니다!


[1]: 쿠키런: 킹덤의 서버 구조는 NDC 발표를 참고하면 됩니다

[2]: 다만 이론적으로 가능하다 해도, 장애 상황이라는 아주 특수한 상황 속에서 얼마나 걸릴지 알 수 없는 이런 접근을 선택하는 게 쉽지는 않았는데요. DB 직접 복구가 불가능할 때를 대비한 로그 기반의 복구를 Plan B로 잡고 데이터 직군 엔지니어들과 서버 개발자분들이 해당 작업을 진행하고 있었습니다. 내가 실패해도 이들이 성공할 거라는 믿음을 갖고 공격적으로 진행할 수 있었습니다. 자세한 이야기는 다른 글에서.

[3]: 여담이지만 LevelDB도 구글의 전설적인 소프트웨어 엔지니어 Jeff Dean의 작품입니다

데브시스터즈는 최고의 인재를 찾고 있습니다.

자세한 내용은 채용 사이트를 확인해주세요!

© 2024 Devsisters Corp. All Rights Reserved.