스토리지 엔진의 추가 성능 개선 지점이 컴팩션 정책에서 I/O 경로로 옮겨갔습니다.
컴팩션 정책을 바꿔 쓰기 증폭을 줄이는 일은 오랫동안 스토리지 엔진 최적화의 기본이었습니다. 2025~2026년 연구와 프로덕션 사례에는 알고리즘을 바꾸지 않고 I/O 경로만 고쳐 큰 이득을 낸 결과도 있습니다. 그래서 소프트웨어 튜닝과 I/O 경로 개선으로 얻을 성능을 나눠 판단해야 합니다.
각 결과는 하드웨어, 워크로드, 기준선이 달라 그대로 더할 수 없습니다. 여러 층의 최적화를 함께 적용한 누적 효과도 검증되지 않았습니다. 이 글의 비교는 병목을 찾는 순서를 정하기 위한 근거입니다.
LSM 트리의 기본 구조와 쓰기 증폭이 왜 생기는지는 LSM 트리와 쓰기 증폭에서 다뤘습니다. 이 글은 그 설명을 바탕으로 어느 층에서 추가 최적화가 가능한지 살펴봅니다.
병목이 위쪽에 없다는 신호
RESYSTANCE 논문(ICDE 2026, arXiv:2603.05162)은 첫 문장에서 전제를 밝힙니다. NVMe SSD 같은 고속 장치가 보급되면서 I/O 병목이 하드웨어에서 소프트웨어로 옮겨갔다는 것입니다. 장치가 빨라진 만큼 커널을 드나드는 소프트웨어 스택의 비용이 상대적으로 커졌다는 뜻입니다.
EcoTune 논문(SIGMOD 2025)은 다른 각도에서 같은 지점을 짚습니다. 이 논문이 인용한 메타의 실제 워크로드 최고 쓰기 속도는 초당 약 45MB인데, 현대 NVMe SSD의 쓰기 대역폭은 초당 2GB를 넘습니다. 쓰기 대역폭이 이만큼 남으면 쓰기 증폭 자체가 쓰기 성능의 제약이 되지 않습니다. EcoTune 저자들이 컴팩션의 목표를 쓰기 증폭 최소화가 아니라 평균 질의 처리량 최대화로 다시 정의한 근거가 이 격차입니다.
두 논문은 서로 다른 문제를 풀지만 같은 전제 위에 서 있습니다. 장치 대역폭은 남고, 그 대역폭에 도달하기까지의 경로가 비용을 결정합니다.
| 관측 | 먼저 의심할 병목 | 확인 방법 |
|---|---|---|
| 장치 대역폭 포화 | 컴팩션과 쓰기 증폭 | 디스크 쓰기량과 애플리케이션 쓰기량 비교 |
| 장치 대역폭 여유, CPU 포화 | 시스템 콜과 메모리 경로 | CPU 프로파일과 시스템 콜 수집 |
| 원격 커밋 지연 | 네트워크 I/O 경로 | 로그 flush 지연과 왕복 시간 측정 |
flowchart TD
subgraph ENGINE["스토리지 엔진 층"]
P["컴팩션 정책<br/>Vertiorizon · EcoTune · ArceKV"]
V["값 배치<br/>Pebble 값 분리"]
C["블록 캐시 · 필터<br/>Mobius · 분할 필터 블록"]
end
subgraph KERNEL["커널 층"]
S["시스템 콜 · 페이지 캐시<br/>RESYSTANCE eBPF · io_uring"]
end
subgraph DEVICE["장치 · 네트워크 층"]
D["로컬 NVMe SSD"]
N["원격 스토리지 · 분리 메모리<br/>O3-LSM"]
end
P --> V --> C --> S
S --> D
S --> N
소프트웨어 튜닝으로 벌 수 있는 몫
알고리즘 층의 최근 성과는 분명합니다. Vertiorizon(SIGMOD 2025)은 레벨을 깊게 쌓는 수직 방식과 넓게 키우는 수평 방식을 결합합니다. RocksDB에 통합한 평가에서 수평 방식 대비 추가 공간 비용이 약 6분의 1이었습니다.
EcoTune은 컴팩션을 자원 투자로 보고 동적 계획법으로 정책을 정해, leveling 대비 평균 질의 처리량을 1.5~3배 올렸습니다. ArceKV는 구조 제약을 없앤 ElasticLSM에 경량 결정 엔진을 붙였습니다. 읽기·쓰기 비율이 바뀌는 평가에서 약 3배 빠른 성능을 보고했습니다.
같은 알고리즘 층에서 데이터 배치도 바꿀 수 있습니다. CockroachDB는 v25.4에서 스토리지 엔진 Pebble에 값 분리를 도입했습니다. 큰 값을 SSTable 밖의 blob 파일에 두고 컴팩션 때는 값 핸들만 복사하는 방식입니다. BIGINT 기본키에 4KiB 값을 upsert하는 벤치마크에서 동일 하드웨어 기준 처리량이 약 47% 늘었고, 값이 작은 워크로드에서는 개선 폭이 줄어든다고 같은 글이 밝힙니다.
파라미터 조정만으로도 성능을 크게 개선할 수 있습니다. 토스는 Flink 상태 백엔드로 쓰는 RocksDB에서 쓰기 증폭을 '배수 10 × (활성 레벨 수 - 1)'로 근사했습니다. 최상위 레벨을 비활성화해 활성 레벨을 3단계에서 2단계로 줄이자 쓰기 증폭이 20에서 10으로 내려갔습니다.
| 지렛대 | 대표 사례 | 바꾸는 것 | 보고된 이득 |
|---|---|---|---|
| 레벨 성장 전략 | Vertiorizon | 트리가 커지는 방향 | 수평 방식 대비 추가 공간 비용 약 1/6 |
| 컴팩션 정책 | EcoTune | 병합 시점과 강도 | leveling 대비 처리량 1.5~3배 |
| 워크로드 적응 | ArceKV | 실행 중 정책 전환 | 동적 워크로드에서 약 3배 |
| 값 배치 | Pebble 값 분리 | 컴팩션이 복사하는 바이트 | 4KiB 값 upsert 처리량 약 47% 증가 |
| 파라미터 튜닝 | 토스 RocksDB | 활성 레벨 수 | 쓰기 증폭 20 → 10 |
이 표의 이득은 모두 워크로드 가정에 묶여 있습니다. 값이 작으면 값 분리의 이득이 줄어듭니다. 워크로드가 고정이면 적응형 구조에는 결정 오버헤드만 남습니다.
커널 안으로 들어간 컴팩션
RESYSTANCE는 LSM 트리 구조도 컴팩션 알고리즘도 건드리지 않습니다. 백그라운드 컴팩션이 읽기 시스템 콜을 대량으로 발생시킨다는 점을 실측으로 짚습니다. 그리고 eBPF와 io_uring으로 핵심 I/O 루틴을 커널 안에서 처리해 사용자·커널 공간 전환을 없앱니다. db_bench와 YCSB, OLTP 워크로드로 평가했습니다.
| 지표 | 기본 RocksDB 대비 |
|---|---|
| 컴팩션 중 평균 시스템 콜 호출 수 | 99% 감소 |
| 컴팩션 소요 시간 | 50% 단축 |
| 쓰기 집약 워크로드 처리량 | 최대 75% 향상 |
| p99 지연 | 40% 감소 |
이 결과는 정책을 바꾸지 않고 실행 경로만 바꿔 얻었습니다. 따라서 알고리즘과 I/O 경로는 서로 다른 최적화 후보가 될 수 있습니다. 두 효과가 독립적으로 누적되는지는 해당 평가가 측정하지 않았으므로, 함께 적용한 결과는 별도 벤치마크로 확인해야 합니다.
| 조합 | 설계상 함께 적용 | 공개 수치로 누적 효과 확인 |
|---|---|---|
| 컴팩션 정책 + 커널 I/O 경로 | 가능 | 확인되지 않음 |
| 값 분리 + 분할 필터 | 가능 | 확인되지 않음 |
| 캐시 축출 + 원격 스토리지 | 가능 | 확인되지 않음 |
프로덕션에서 실제로 걸린 자리
토스 사례에서 더 흥미로운 부분은 쓰기 증폭 튜닝 다음에 나옵니다. 컴팩션이 밀리고 CPU가 포화된 앱을 프로파일링했더니, 수집한 1,766개 샘플 중 96.2%가 필터 블록을 디스크에서 읽는 경로에 몰려 있었습니다. 컴팩션 정책과는 상관이 없는 병목이었습니다.
원인은 I/O 경로 설정이었습니다. 쿠버네티스에서 컨테이너 메모리 사용을 예측 가능하게 만들려고 Direct I/O를 켜면 OS 페이지 캐시를 완전히 우회합니다. 블록 캐시에 없는 필터 블록은 매번 디스크에서 읽어야 하는데, SST 파일당 약 800만 개 키에 키당 14비트를 쓰면 단일 필터 블록이 약 15MB에 달합니다. 캐시 미스 한 번이 15MB 읽기가 됩니다.
해결 방법은 필터 블록을 약 4KB 단위로 쪼개는 partitioned-index-filters 활성화였습니다. 이 설정으로 미스당 읽기량이 약 2,750배 줄었습니다. 알고리즘을 바꾼 조치가 아니라 데이터가 커널과 장치를 지나는 경로를 바꾼 조치입니다. 페이지 캐시와 I/O 처리 방식의 기본은 운영체제 IO 관리와 파일 시스템에 정리해 뒀습니다.
I/O 경로가 네트워크를 건너갈 때
컴퓨트와 스토리지를 분리하면 I/O 경로에 네트워크가 끼어듭니다. PostgreSQL과 RocksDB로 구현한 SIGMOD 2024 실증 연구는 이 비용을 단계별로 측정했습니다(2026-09 확인). 버퍼 없이 원격 디스크만 쓰는 가장 단순한 분리에서 읽기는 16.4배, 쓰기는 17.9배 느려졌습니다. 8GB 버퍼로 히트율 80%를 확보하면 읽기 격차는 1.8배까지 줄지만, 쓰기는 버퍼를 아무리 키워도 개선되지 않습니다.
쓰기가 버퍼에 반응하지 않는 이유는 커밋마다 로그를 원격으로 플러시해야 하기 때문입니다. 논문은 버퍼 히트율을 99.5%까지 올려도 쓰기 성능이 크게 나아지지 않는다고 결론짓습니다. 이 논문을 정리한 Murat Demirbas의 글은 모든 최적화를 적용한 뒤에도 쓰기 처리량이 단일 노드의 약 50%에 머문다고 그래프에서 읽었습니다(추정). 알고리즘 층의 어떤 튜닝도 이 격차를 메우지 못하는 이유는 경로 자체가 바뀌었기 때문입니다.
오프로딩 범위를 넓힌 연구도 있습니다. O3-LSM(SIGMOD 2026)은 공유 분리 메모리를 써서 memtable과 flush, 컴팩션을 세 층으로 오프로딩합니다. 컴팩션만 오프로딩하던 기존 분리형 구현들과 비교해 쓰기 처리량 최대 4.5배, 범위 질의 최대 5.2배, p99 지연 최대 76% 감소를 보고했습니다.
캐시 축출에서 먼저 벌어진 같은 이동
같은 이동이 스토리지 엔진 안의 다른 부품에서도 일어났습니다. S3-FIFO와 SIEVE는 무엇을 지울지를 다시 설계해 미스율을 낮췄습니다. 그다음 나온 Mobius(SIGMETRICS 2025)는 히트율을 비슷하게 유지한 채 락 경합만 제거했습니다. 두 개의 lock-free FIFO 큐와 연속 감지 기법으로, 기존 최고 수준 기법 대비 동시 처리량을 1.2배에서 8.5배까지 올렸습니다.
Mobius 저자들이 밝힌 문제 인식은 RESYSTANCE와 겹칩니다. 장치는 빨라지는데 기존 축출 정책이 굵은 단위 락과 복잡한 자료구조에 의존해 동시 처리량을 내지 못한다는 진단입니다. 축출 알고리즘을 그대로 두고 접근 경로만 바꿔 배수 이득을 냈다는 형태도 같습니다. 이 흐름은 LRU를 대체하는 FIFO 캐시 축출에서 자세히 다뤘습니다.
Mobius가 CacheLib과 RocksDB에 실제로 구현됐다는 점도 이 관찰을 뒷받침합니다. 캐시 축출과 컴팩션은 서로 다른 서브시스템인데 병목이 같은 방향으로 옮겨갔습니다.
이 주장이 깨지는 조건
첫째, 장치가 느리면 성립하지 않습니다. RESYSTANCE의 전제는 고속 스토리지에서 소프트웨어 스택이 상대적으로 비싸졌다는 것입니다. 회전 디스크나 대역폭이 낮은 원격 볼륨에서는 여전히 장치가 병목이고, 그때는 쓰기 증폭을 줄이는 쪽이 직접 이득입니다.
둘째, 워크로드가 알고리즘의 전제를 깨면 알고리즘이 다시 결정적입니다. SOLAR 연구(arXiv:2607.00394)는 LLM 에이전트의 의미 검색 버퍼를 다룹니다. 시간적 지역성과 빈도 집중이 없는 이런 워크로드에서는 LRU와 LFU가 단순 FIFO보다도 못했습니다. 학습 증강 교체 방식은 타이트한 캐시 크기 기준 FIFO 대비 5~75%의 상대 개선을 냈습니다.
셋째, 이 글의 수치를 더하면 안 됩니다. 각 수치는 서로 다른 벤치마크와 하드웨어, 서로 다른 기준선에서 나왔고 2026-09 기준으로 각 논문 초록과 공개 글에서 확인한 값입니다. 세 층을 동시에 적용했을 때의 누적 효과를 측정한 연구는 찾지 못했습니다. 축이 겹치지 않는다는 것은 설계상의 주장이지 측정된 사실이 아닙니다.
넷째, I/O 경로 최적화는 비용을 없애지 않고 옮깁니다. eBPF와 io_uring은 커널 버전에 의존하고, Direct I/O는 토스 사례처럼 새로운 캐시 미스 문제를 만듭니다. 분리 스토리지는 네트워크 가용성을 데이터베이스 가용성에 직접 연결합니다.
| 조건 | 주된 병목 | 우선 조치 |
|---|---|---|
| 느린 장치 또는 대역폭 포화 | 장치 쓰기 | 쓰기 증폭과 컴팩션 조정 |
| 빠른 장치와 높은 시스템 콜 비용 | 소프트웨어 경로 | I/O 호출 경로 프로파일링 |
| 지역성 없는 캐시 워크로드 | 교체 알고리즘 | 워크로드에 맞는 정책 검증 |
| 분리 스토리지 | 네트워크 커밋 | 로그 flush 경로 측정 |
층별 점검 순서
측정 결과를 기준으로 최적화 순서를 정합니다. 아래 항목을 확인하면 어느 층을 우선 개선할지 판단할 수 있습니다.
- 장치 쓰기 대역폭이 실제로 포화되는지 봅니다. 포화되면 쓰기 증폭 감소가 직접 이득이므로 컴팩션 정책과 활성 레벨 수부터 조정합니다.
- 대역폭이 남는데 처리량이 부족하면 CPU 프로파일을 확인합니다. 시스템 콜과 캐시 미스가 상위에 있으면 I/O 경로를 점검합니다.
- 값 크기 분포를 잽니다. 큰 값이 컴팩션 바이트의 상당 부분을 차지하면 값 분리를 검토합니다.
- 스레드를 늘려도 처리량이 비례하지 않으면 공유 자료구조의 락을 의심합니다. 캐시와 큐 구현을 확인합니다.
- 네트워크가 I/O 경로에 있으면 쓰기 커밋 경로부터 봅니다. 읽기 버퍼만 키워서는 원격 로그 flush 지연이 줄지 않습니다.
| 관측된 신호 | 손댈 층 | 참고할 수치 |
|---|---|---|
| 쓰기 대역폭 포화, 쓰기 증폭 두 자릿수 | 컴팩션 정책·레벨 수 | 토스 쓰기 증폭 20 → 10 |
| 대역폭 여유, 시스템 콜이 프로파일 상위 | 커널 I/O 경로 | RESYSTANCE 컴팩션 시간 50% 단축 |
| 큰 값이 컴팩션 바이트를 지배 | 값 배치 | Pebble 처리량 약 47% 증가 |
| 캐시 미스가 CPU를 지배 | 캐시 구조·필터 배치 | 토스 미스당 읽기 약 2,750배 감소 |
| 스레드 확장에서 처리량 정체 | 동시성 구조 | Mobius 동시 처리량 1.2~8.5배 |
| 원격 스토리지 쓰기 지연 | 아키텍처 | 버퍼를 키워도 쓰기 미개선, 단일 노드 대비 약 50% (추정) |
정리
고속 장치에서는 컴팩션 정책과 함께 I/O 경로도 주요 최적화 지점이 됩니다. 장치가 느리거나 대역폭이 포화되면 쓰기 증폭을 먼저 줄여야 합니다. 대역폭이 남는다면 CPU 프로파일에서 시스템 콜과 캐시 미스 경로를 확인합니다.
논문별 개선 수치는 서로 다른 조건과 기준선에서 측정됐습니다. 여러 층을 함께 적용한 누적 효과는 별도 측정이 필요합니다. 운영 결정은 각 수치를 더하는 방식이 아니라 현재 시스템의 병목을 측정하는 데서 시작합니다.