신규: Helius가 Light Protocol을 인수했습니다
RocksDB란? 임베디드 키-값 저장소
블로그/엔지니어링

RocksDB란? 임베디드 키-값 저장소

Developer Experience EngineerX의 0xIchigoLinkedIn의 0xIchigoGitHub의 0xIchigo
읽는 데 9분

RocksDB는 가장 널리 사용되는 스토리지 시스템 중 하나지만, 이를 직접 구성하는 경우는 거의 없습니다. Kafka, MyRocks 기반 MySQL, TiDB, YugabyteDB, Ceph뿐 아니라 대다수 Solana 검증인의 원장 아래에서 작동합니다. 

이처럼 핵심적인 역할을 하는 기술임에도 관련 자료는 놀라울 정도로 부족합니다. 

공식 문서는 훌륭한 참고 자료지만 입문용으로는 적합하지 않습니다. 다른 설명 자료도 대부분 독자가 LSM 트리 같은 개념을 이미 안다고 가정합니다.

이 글은 RocksDB의 내부 작동 방식을 살펴보는 시리즈의 첫 번째 글입니다. 가장 기본적인 질문부터 시작합니다.

RocksDB란?

RocksDB는 빠른 저장 성능에 최적화된 임베디드 영구 키-값 저장소입니다. 임의의 바이트 배열을 키와 값으로 받아 정렬한 후 디스크에 영구 저장합니다. 

가장 먼저 알아야 할 핵심은 RocksDB가 서버가 아닌 라이브러리라는 점입니다. 연결할 프로세스도, 열어야 할 포트도, 배워야 할 쿼리 언어도 없습니다.

RocksDB는 애플리케이션에 통합되어 해당 프로세스 안에서 실행되며 로컬 디스크의 파일을 읽고 씁니다. 애플리케이션 코드와 처리 대상 데이터 사이에 네트워크 홉이 없기 때문에 빠릅니다. 또한 복제, 샤딩, 쿼리를 상위 시스템에 의도적으로 맡기므로 구성이 간결합니다.

간단히 말해 RocksDB는 스토리지 엔진입니다. TiDB와 YugabyteDB 같은 데이터베이스의 기반이 되는 구성 요소입니다.

RocksDB와 LevelDB는 같은가요?

아닙니다. 하지만 두 프로젝트는 같은 코드베이스에서 출발했습니다. RocksDB는 Google의 Jeff Dean과 Sanjay Ghemawat이 만든 경량 키-값 저장소 LevelDB의 포크로 2012년에 시작되었습니다. 

LevelDB는 브라우저의 IndexedDB 백엔드나 단일 임베디드 기기처럼 제한된 환경을 위해 개발되었습니다. 이에 맞춰 단일 스레드 컴팩션, 보수적인 메모리 사용, 최소한의 튜닝 등 다양한 설계 결정을 내렸습니다.

현재 Meta인 Facebook의 엔지니어들은 이 기반을 서버 워크로드에 맞게 재설계했습니다. 지속적인 쓰기 부하 속에서 메모리보다 훨씬 큰 데이터세트를 처리하면서 최신 하드웨어 성능을 최대한 활용하는 것이 목표였습니다. 

RocksDB는 2013년에 오픈 소스로 공개된 후 LevelDB와 크게 달라졌습니다. 멀티스레드 컴팩션, 컬럼 패밀리, 트랜잭션, 백업, 병합 연산자, 교체 가능한 컴팩션 방식과 방대한 튜닝 옵션을 제공합니다.

LevelDB와 RocksDB는 비슷한 계보를 공유하지만, 두 기술을 서로 바꿔 쓸 수 있다고 보기는 이미 10년 전부터 어려워졌습니다.

RocksDB는 어떻게 작동하나요?

RocksDB는 읽기 단순성을 일부 포기하고 쓰기 처리량을 높이는 데이터 구조인 **로그 구조 병합 트리(LSM 트리)**를 기반으로 합니다. 

기본적으로 유입되는 쓰기 작업은 memtable이라는 인메모리 버퍼에 기록됩니다. 동시에 내구성을 위해 디스크의 선행 기록 로그에도 추가됩니다. 

memtable이 가득 차면 동결된 후 **Sorted String Table (SST)**이라는 변경 불가능한 정렬 파일로 디스크에 플러시됩니다. SST 파일은 계층 구조에 누적됩니다. 컴팩션이라는 백그라운드 프로세스가 이를 지속적으로 병합하고 덮어쓴 값과 삭제된 키를 제거하여 깔끔하게 정렬된 영구 구조를 유지합니다.

읽기 작업은 먼저 memtable을 확인한 다음 SST 파일의 여러 레벨을 차례로 탐색합니다. Bloom 필터를 사용하면 해당 키가 존재할 수 없는 파일을 건너뛸 수 있습니다. 블록 캐시는 자주 사용하는 데이터를 메모리에 유지하므로 대부분의 읽기 작업은 한두 개 이상의 파일에 접근하지 않습니다.

LSM 트리와 B-트리의 차이는 무엇인가요?

대부분의 전통적인 데이터베이스가 사용하는 구조인 B-트리는 데이터를 제자리에서 갱신하므로 무작위 쓰기가 디스크 전반에 흩어집니다. LSM 트리는 데이터를 추가하고 일괄 처리하여 대규모 순차 쓰기를 수행합니다. 이는 SSD와 대량 수집 워크로드에 적합합니다. 대신 키의 현재 값이 여러 파일에 걸쳐 있을 수 있으므로 컴팩션, Bloom 필터, 캐싱으로 이러한 비용을 제한해야 합니다. 

LSM 트리의 모든 설계 결정은 결국 세 가지 압력 사이에서 균형을 찾는 과정입니다.

  • 쓰기 증폭
  • 읽기 증폭
  • 공간 증폭

이 중 두 가지를 개선하면 나머지 하나가 악화되는 경향이 있습니다. 

이 삼각관계는 RocksDB 튜닝을 이해하는 데 가장 중요한 사고 모델입니다. 

RocksDB는 어디에 사용되나요?

RocksDB는 별도의 데이터베이스 서버를 운영하는 오버헤드 없이 로컬 디스크에 빠르고 내구성 있는 정렬된 키-값 스토리지가 필요한 곳에서 사용됩니다. 범주보다 구체적인 사례를 보면 이 패턴을 더 쉽게 이해할 수 있습니다.

Kafka Streams는 각 처리 작업의 상태(진행 중인 집계, 조인, 윈도우 연산)를 로컬 RocksDB 저장소에 보관합니다. 따라서 상태가 메모리보다 커져도 처리할 수 있고, 조회할 때마다 네트워크 왕복을 추가하지 않으면서 재시작 후에도 상태를 유지할 수 있습니다.

Meta의 ZippyDB는 RocksDB에 복제 계층, 샤드 관리, 구성 서비스를 결합하여 완전 관리형 분산 키-값 저장소를 제공합니다. 역할도 명확합니다. RocksDB가 스토리지를 담당하고 서버에 필요한 모든 기능은 그 주변에 구축됩니다.

TiDB의 스토리지 계층은 모든 노드에서 분산 SQL 데이터베이스의 기반 엔진으로 RocksDB를 실행합니다. 테이블 구조를 키 접두사로 인코딩하므로 테이블 스캔은 정렬된 키에 대한 하나의 연속 읽기가 됩니다.

Agave 기반의 모든 Solana 검증인은 연속된 원장 데이터가 디스크에서 서로 인접하도록 슬롯을 키로 사용해 원장을 RocksDB에 기록합니다.

마지막 두 사례가 흥미로운 이유는 키가 기본적으로 바이트 단위 또는 사용자 지정 비교 기준에 따라 정렬되어 저장되기 때문입니다. 덕분에 범위 스캔과 접두사 조회를 효율적으로 수행할 수 있습니다.

RocksDB 기반 시스템의 실제 스키마 설계는 상당 부분 이러한 정렬 순서를 활용하는 데 달려 있습니다.

누가 RocksDB를 사용하나요?

위 시스템 외에도 RocksDB는 임베디드 방식으로 작동하며 실전에서 검증된 쓰기 최적화 스토리지 엔진이 필요한 모든 시스템의 기반이 됩니다. 팀이 처음부터 엔진을 개발하는 대신 RocksDB를 선택하면 Meta가 10년 넘게 프로덕션에서 축적한 안정성을 그대로 활용할 수 있습니다.

대표적인 사례는 다음과 같습니다.

RocksDB는 주로 SSD에서 실행되는 쓰기 집약적이고 메모리보다 큰 워크로드에 사용됩니다. 이 내용은 글 후반에서 다시 살펴봅니다.

Solana는 RocksDB를 어떻게 사용하나요?

Solana는 속도, 효율성, 소비자 애플리케이션에 중점을 둔 고성능 저지연 지분증명 블록체인입니다. Solana의 원장은 RocksDB에 저장됩니다. Agave 검증인 클라이언트는 독립적으로 튜닝 가능한 키 공간으로 분할된 RocksDB 데이터베이스를 포함하는 구성 요소인 Blockstore에 원장을 저장합니다. 

별도의 컬럼 패밀리는 샤드 데이터와 샤드 삭제 코딩 데이터(즉, 네트워크를 통해 수신되는 원장 데이터의 원시 단위), 트랜잭션 상태, 주소-서명 인덱스, 기타 메타데이터를 보관합니다.

검증인의 쓰기 패턴은 일반적인 데이터베이스 워크로드와 비교하면 극단적입니다. 검증인은 거의 단조롭게 증가하는 슬롯 번호를 키로 사용해 네트워크 회선 속도로 샤드를 끊임없이, 영원히 수집해야 합니다. 프루닝하지 않은 원장 아카이브는 수백 테라바이트를 훌쩍 넘으며 매년 최소 수십 테라바이트씩 증가합니다.

레벨 기반 컴팩션에서는 샤드 컬럼 패밀리가 지나치게 많은 백그라운드 컴팩션 작업을 생성하여 검증인이 쓰기 중단에 직면하기 시작했습니다. 쓰기 중단은 컴팩션이 지연될 때 데이터 수집 속도를 낮추는 RocksDB의 기본 메커니즘입니다. 

2021년경 샤드 컬럼 패밀리는 FIFO 컴팩션으로 전환되었습니다. 크기 제한에 도달하면 가장 오래된 파일을 삭제하는 단순한 방식입니다. FIFO는 일반적인 워크로드에는 대개 안전하지 않습니다. 하지만 샤드 키는 거의 단조롭게 증가하는 슬롯 순서로 수신되므로 검증인은 가장 오래된 슬롯이 담긴 가장 오래된 파일을 삭제할 수 있었습니다. 워크로드 형태에 거의 완벽하게 부합하는 컴팩션 방식이었습니다.

이후 레벨 기반 컴팩션 최적화로 I/O 증폭이 줄면서 FIFO의 이점이 사라졌습니다. 이에 따라 FIFO 경로는 2024년 6월에 사용 중단되었고 같은 해 11월에 제거되었습니다.

Jump Crypto가 C로 작성한 Solana 검증인 클라이언트 Firedancer는 RocksDB를 완전히 배제하고 자체 개발한 전용 스토리지 계층을 사용합니다.

처음부터 개발한 스토리지 엔진이 10년 넘게 안정화되고 튜닝된 RocksDB보다 빠를 수 있을지는 검증인 엔지니어링 분야의 흥미로운 미해결 문제입니다.

RocksDB가 적합하지 않은 경우는 언제인가요?

RocksDB가 적합하지 않은 경우는 그 보편성에서 예상하는 것보다 많습니다. 서로 어떻게 영향을 주는지 명확하지 않은 옵션이 수백 개에 달해 튜닝 범위가 방대합니다. 따라서 잘못된 튜닝은 예외가 아니라 흔한 결과입니다. 

게다가 RocksDB의 기본값은 적절할 뿐 최적은 아닙니다. 실질적인 성능 향상을 얻으려면 개발자가 앞서 설명한 증폭 간의 절충 관계를 이해해야 합니다. 지속적인 대량 수집이 백그라운드 컴팩션 속도를 추월하면 쓰기 중단이 발생할 수 있으며, 이는 시스템이 가장 바쁠 때 나타나는 경향이 있습니다.

또한 RocksDB는 서버가 아니라 라이브러리입니다. 일반적인 데이터베이스 서버가 제공하는 복제, 샤딩, 백업, 접근 제어, 쿼리 계층, 운영 도구 등을 모두 직접 구축해야 합니다.

워크로드 형태도 매우 중요합니다. 

RocksDB는 행 지향 방식으로 포인트 조회와 범위 스캔을 처리하는 엔진입니다. 넓은 범위의 데이터를 스캔하고 여러 컬럼을 집계하는 분석 워크로드에는 ClickHouse 같은 컬럼형 저장소가 더 적합합니다.

그렇다고 RocksDB를 피해야 하는 것은 아닙니다. 신중하게 선택해야 한다는 뜻입니다. Helius는 아카이브 계층을 재설계할 때 이러한 위험을 직접 평가했지만 결국 RocksDB를 선택했습니다. 워크로드가 RocksDB의 설계 목적과 정확히 일치했기 때문입니다. 추가 작업이 많은 대규모 데이터세트에서 포인트 조회와 좁은 범위 스캔을 수행하는 형태였습니다. 

결론

RocksDB는 운영 편의성 대신 로컬 디스크에서의 순수 성능을 선택한 임베디드 영구 정렬 키-값 저장소입니다. LevelDB에서 시작해 Meta에서 SSD와 멀티코어 시스템에 맞게 강화되었으며, 이제 스트림 프로세서와 분산 SQL 데이터베이스부터 스토리지 클러스터와 Solana 원장까지 전 세계 다양한 시스템 안에서 작동합니다.

대규모 고성능 RocksDB 환경을 구축하는 일이 흥미롭게 느껴진다면 저희와 함께하세요.

Solana는 글로벌 금융의 결제 계층이 되기 위해 빠르게 성장하고 있으며, 그 기반 인프라는 이 시리즈에서 설명하는 기술로 작동합니다. 최고의 하드웨어를 활용해 전 세계 규모의 어려운 시스템 문제를 해결하세요.

현재 엔지니어링 팀 전반에서 인재를 채용하고 있습니다. helius.dev/careers에서 모든 채용 공고를 확인하세요.

Helius 구독하기

최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요