💡 핵심 인사이트 LSM-Tree(Log-Structured Merge-Tree)는 **"쓰기 성능을 극대화하기 위해 데이터를 먼저 메모리(MemTable)에 저장하고, 일정 크기가 되면 디스크(SSTable)로_FLUSH_한 후,Background에서 병합(Compaction)하는 쓰기 최적화 자료구조"**입니다. 전통적 B-Tree가 읽기 우선(Read-Optimized)이라면, LSM-Tree는 쓰기 우선(Write-Optimized)입니다. Cassandra, RocksDB, LevelDB, MongoDB의 WiredTiger 등이 LSM-Tree를採用しており、쓰기放量 обработка에 강한noSQL의 핵심 저장 엔진입니다.


Ⅰ. B-Tree의 쓰기 문제: 왜 LSM-Tree가诞았나?

[B-Tree의 쓰기 동작]

  B-Tree에서 UPDATE 발생 시:
  1. 해당 leaf 노드를 디스크에서 읽음 (Random I/O)
  2. 메모리에서 값 수정
  3. 해당 leaf 노드를 디스크에 다시 기록 (Random I/O)
  → 같은 위치를 읽고 쓰기를 반복 (Read-Modify-Write)

  문제점:
  - Random I/O가 연속 I/O보다数百倍 느림
  - 쓰기 시 마다 디스크 탐색(seek) 필요
  - SSDs도 Random 쓰기 오버헤드는 존재

LSM-Tree의 혁신: 모든 쓰기를 순차적(sequential)으로만 처리하자!


II. LSM-Tree의 핵심 자료구조: MemTable + SSTable

[LSM-Tree 아키텍처]

  쓰기 요청 (Write)
        │
        ▼
  ┌─────────────────────────────────┐
  │       MemTable (메모리)          │
  │  - 건건이 메모리에 기록 (순차)     │
  │  - 내부: Red-Black Tree 또는     │
  │    Skip List                    │
  │  - L0: 임계치 도달 시            │
  │    → Immutable MemTable로 전환   │
  │    → 새 MemTable 생성            │
  └─────────────────────────────────┘
        │ (임계치 도달 시)
        ▼
  ┌─────────────────────────────────┐
  │      L0: Level 0 (SSTable)       │
  │  - Immutable MemTable를         │
  │    디스크에Flush                │
  │  - 아직 정렬되지 않은 상태        │
  └─────────────────────────────────┘
        │ (Compaction 시)
        ▼
  ┌─────────────────────────────────┐
  │      L1: Level 1 (SSTable)      │
  │  - L0 파일들 병합 + 정렬         │
  │  - 키 기준 정렬된 상태            │
  └─────────────────────────────────┘
        │
        ▼
  ┌─────────────────────────────────┐
  │      L2: Level 2 (SSTable)      │
  │  - L1과 병합, 더 큰 파일로        │
  └─────────────────────────────────┘
        │
        ...

MemTable (메모리 계층)

# MemTable 구현 예시 (Skip List)
import random

class SkipList:
    """멀티레벨 연결 리스트로 O(log n) 검색"""
    def __init__(self, max_level=16):
        self.max_level = max_level
        self.header = Node(key=None, value=None, level=max_level)
        self.level = 0

    def insert(self, key, value):
        # 1. 해당 위치를 찾음 (수평으로 가로-scan)
        # 2. 새 노드를 위에서 아래로 수평插入
        # 3. Random 레벨 선택 (역사적 비율: 1/2^level)
        pass

    def flush_to_disk(self):
        # 메모리의 Skip List를 정렬된 형태로 SSTable로 변환
        pass

SSTable (Static Sorted String Table)

[SSTable 구조]

  ┌──────────────────────────────────────────────────────┐
  │                    SSTable File                       │
  │  ┌────────────┐                                       │
  │  │ Data Block │  [key1:value1][key2:value2]...       │
  │  │ (실제 데이  │  - 키로 정렬된 상태                   │
  │  │  타 저장)   │  - 블럭 단위 압축 (Snappy, Zstd)     │
  │  └────────────┘                                       │
  │  ┌────────────┐                                       │
  │  │ Index Block│  [key: 오프셋] 들의集合               │
  │  │ (색인)      │  - 특정 키 빠르게 검색 가능           │
  │  └────────────┘                                       │
  │  ┌────────────┐                                       │
  │  │ Bloom Filter│ - 키 존재 여부 bloom filter로高速 판별│
  │  │             │  - 없으면 디스크 I/O 불필요         │
  │  └────────────┘                                       │
  │  ┌────────────┐                                       │
  │  │  Footer     │  - 메타데이터 (인덱스, 필터 위치 등)  │
  │  └────────────┘                                       │
  └──────────────────────────────────────────────────────┘

III. Compaction (콤팩션): 쓰레기 수집의艺术

콤팩션의 종류

1. Size-Tiered Compaction (STCS) - Cassandra 기본:

[Size-Tiered Compaction]

  Level 0: SSTable 1 (4MB)
  Level 1: SSTable A, B (8MB each)   ← 크기 유사
  Level 2: SSTable C, D, E, F (16MB each)
  Level 3: SSTable G~N (32MB each)

  Trigger: 각 레벨이 N개 파일이 되면 → 상위 레벨로 병합
  → 항상 모든 레벨의 파일이 유사한 크기로 유지

2. Level-Tiered Compaction (LTCS) - Cassandra 3.0+:

[Level-Tiered Compaction]

  Level 0: 최대 4개 파일 (4MB)   ← 수평 확장
  Level 1: 최대 10개 파일 (40MB)   ← 10배 크기
  Level 2: 최대 100개 파일 (400MB)  ← 10배 크기
  Level 3: 최대 1000개 파일 (4GB)

  Trigger: 각 레벨의 모든 파일을 상위 레벨의 파일과정밀히 병합
  → 상위 레벨로 갈수록 파일 수 10배 증가

콤팩션 과정

[Compaction 상세 과정]

  상황: Level N에서 병합 시작

  Step 1: 병합 대상 파일 선택
  ┌──────────────────────────────────────┐
  │ Level N: [A, B, C, D] 파일들        │
  │ (모두 키 범위가 重複)                 │
  └──────────────────────────────────────┘

  Step 2: K-way 병합 정렬 (N=4 → 4-웨이 머지)
  ┌──────────────────────────────────────┐
  │  A: key1=X, key3=Y, key5=Z           │
  │  B: key1=X'(삭제 표시), key4=W      │
  │  C: key2=P, key3=Y'(업데이트)       │
  │  D: key5=Z'                         │
  │                                    │
  │  ↓ 4-웨이 머지 소트                 │
  │  key1=X' → X' (삭제가 최신)          │
  │  key2=P                              │
  │  key3=Y' → Y' (업데이트가 최신)       │
  │  key4=W                              │
  │  key5=Z' → Z' (업데이트가 최신)       │
  └──────────────────────────────────────┘

  Step 3: 결과 파일 생성 (Level N+1)
  ┌──────────────────────────────────────┐
  │ 새 SSTable: [key2:P, key3:Y', key4:W, key5:Z'] │
  │ ( Tombstone: 삭제된 key1은 기록 안 함) │
  └──────────────────────────────────────┘

IV. 쓰기 성능 vs 읽기 성능의 트레이드오프

[LSM-Tree의 성능 특성]

  쓰기 (Write):
  ✅ 장점:
  - 모든 쓰기가 MemTable에만 순차 기록 (Random I/O 없음)
  - 디스크 Flush도 순차적
  - 쓰기 성능: B-Tree 比 10~100배 향상

  ⚠️ 단점:
  - 삭제도 쓰기(Tombstone)로 처리 → 내부碎片 발생
  - Compaction 시 I/O 오버헤드 발생 (Write Amplification)
  - 너무 많은 Compact가 발생하면 쓰기rate 저하

  읽기 (Read):
  ⚠️ 단점:
  - 읽을 때 MemTable → L0 → L1 → ... 순으로 탐색
  - 여러 레벨에 같은 키가 존재 → 最新 값 찾을 때까지 全 레벨 확인
  - (Bloom Filter로 해결)

  ✅ 장점:
  - Bloom Filter로 존재하지 않는 키는快速排除
  - Compaction으로 레벨이 깊어져도 메모리/IO 효율改善

Write Amplification:

  • 원본 데이터 1MB를写入해도, Compaction 과정에서 수 MB의 디스크 쓰기 발생
  • 일반적으로 10~50배 (저장 미디어 수명 영향)

Ⅴ. LSM-Tree 적용 시 고려사항と 📢 비유

LSM-Tree를 采用하는 시스템:

  • Apache Cassandra: STCS/LTCS 선택 가능
  • RocksDB: Level-티어드 (Facebook الأصل)
  • LevelDB: Google의原始 구현
  • MongoDB (WiredTiger): LSM-Tree 지원 (SNappy 압축)
  • InfluxDB: 시계열 데이터용 LSM-Tree 변형

B-Tree vs LSM-Tree 선택:

시나리오추천
쓰기 많은ワーク로드 (IoT, 로그)LSM-Tree
읽기 많은ワーク로드 (Web 서비스)B-Tree
순차 쓰기가 많은 경우 (analytics)LSM-Tree
임의 접근 읽기가 많은 경우B-Tree
짧은 TTL (데이터很快就过期)LSM-Tree

📢 섹션 요약 비유: LSM-Tree는 **"편의점 진열대"**와 같습니다. 새 물건이 들어오면 진열대에 바로 올리는 것이 아니라, **"먼저 창고(메모리)에 쌓아두었다가, 창고가 차면 진열대(SSTable)로 한 꺼내음"**으로 효율적으로管理합니다. 진열대가 다 차면 (Compaction) **"모든 물건을 한 꺼내어新しい날부터 순서대로 다시 진열"**합니다. 이때 先入れ先出し(FIFO)처럼 오래된 것을 뒤로 미뤄두고 새로운 것을 앞에 둡니다. B-Tree가 **"客人을 위해 물건을 찾을 때 진열대 어딜 가도 바로 찾을 수 있다"**는 장점이 있지만, "새 물건 올릴 때마다 어딘가 비워야 하는" 번거로움이 있습니다. LSM-Tree는 "일단 창고에 차곡차곡 쌓아두고, 정기적으로 정리整列하는" 것으로 쓰기 효율을 극대화합니다. "정리하는 시간(Compaction)은 들지만,平日里货物摆放(쓰기)는 빠른" 것이 핵심입니다.