RocksDB LSM-Tree의 쓰기 증폭(Write Amplification) 지옥과 Leveled Compaction 전환기
시계열(Time-series) 로그 데이터를 미친 듯이 쏟아내는 인프라 수집 서버의 스토리지 엔진으로 RocksDB를 채택했으나, 어느 날부터 디스크 I/O 가 터져 나가는 현상을 마주했습니다. 저희가 쓰는 실제 데이터는 초당 10MB인데, 스토리지 어레이를 모니터링해보면 디스크 볼륨 쓰기 대역폭은 100MB/s를 돌파하고 있었습니다. 내가 쓴 데이터보다 10배나 많은 스토리지 백그라운드 쓰기가 강제 발생하는, 전형적인 LSM-Tree의 쓰기 증폭(Write Amplification) 현상이었습니다.
LSM-Tree 엔진은 빠른 쓰기를 위해 데이터를 B-Tree 처럼 기존 디스크 공간을 비집고 업데이트하지 않고 무조건 새 공간에 append-only 방식의 불변 속성(SSTable) 배열로 쌓아 올립니다. 그러다 파일이 많아지면 조회 속도(읽기 증폭)를 보장하기 위해, 백그라운드 스레드가 조각난 파일들을 하나로 병합(Compaction)하는 고된 막노동을 끊임없이 수행합니다. 문제의 근원은 RocksDB의 기본 설정인 Tiered 압축 방식에 있었습니다. 이 방식은 같은 레벨의 거대한 파일들이 찰 때까지 기다렸다가 단번에 무식하게 한 레벨을 다 쓸어 담아 다음 층으로 병합하려 하기에, 디스크 IO 성능 스파이크가 폭발하듯 튀어 올랐습니다.
저는 며칠간의 옵션 프로파일링 끝에, 이 구조를 Leveled Compaction 전략으로 완전히 스위칭 시켜버렸습니다. Leveled 방식은 파일을 작은 단위(예: 64MB)로 아주 균일하게 토막내어 관리하고, 아래층으로 데이터를 내릴 때 겹치는 딱 그 구간의 파일 한두 개만 핀포인트로 병합하는 우아한 알고리즘입니다. 그 결과 일시적인 쓰기 폭발 스파이크는 완만해졌고 조각 모음에 휩쓸려 파괴되는 낭비성 디스크 대역폭을 70% 가까이 회수해 냈습니다. 분산 스토리지 세계에서 백엔드 어플리케이션 개발자는 결국 내부 동작을 이해하는 DB 튜너로 성장할 수밖에 없음을 재차 깨달은 순간입니다.
Related Posts
JVM JIT 컴파일러의 극단적 런타임 최적화: 탈출 분석(Escape Analysis)과 스칼라 치환의 마법
정적 컴파일 언어를 압도하는 자바 머신의 동적 스크립트 프로파일링 및 객체 힙 버림 최적화 기법.
리눅스 eBPF와 XDP를 활용한 커널 바이패스(Kernel Bypass) 초저지연 패킷 필터링 아키텍처
운영체제 네트워크 스택의 병목을 우회하여 디바이스 드라이버 레벨에서 직접 샌드박스 코드를 주입하는 eBPF의 혁명.
스플릿 브레인(Split-Brain) 붕괴를 막는 분산 락(Distributed Lock) 시스템과 펜싱(Fencing) 토큰의 도입
Zookeeper, Redis Redlock의 시계 위임 맹점을 찌르는 가비지 컬렉션 시간 정지(Stop-the-World) 현상 롤백 설계.