RocksDB 為什麼重要#

很少人直接用 RocksDB,但它在「現代儲存底層」幾乎無處不在:

系統用 RocksDB 做什麼
MyRocks(Facebook MySQL)InnoDB 的替代儲存引擎
TiDB / TiKV每個 region 的本地儲存
CockroachDB每個 range 的本地儲存
Kafka Streamsstate store
FlinkRocksDB state backend
Cassandra(部分)memtable 後端
Druidsegment storage
Cephmetadata 儲存

RocksDB 是 Facebook 從 LevelDB(Google)fork 出來、專為 SSD 與大規模設計的嵌入式 KV 儲存引擎。

LSM-Tree:與 B+ Tree 的對抗#

關係型資料庫(MySQL InnoDB、PG)用 B+ Tree。為什麼 RocksDB 不用?

B+ Tree                      LSM-Tree
─────────                    ──────────
寫:找葉子 + in-place 修改     寫:append 到 log + memtable
讀:log(N) 查找葉子           讀:multi-level 查找
更新:原地改                  更新:寫新版 + 後台合併
刪除:原地標記                刪除:寫 tombstone + 後台合併

權衡:

維度B+ TreeLSM-Tree
寫入random IO(壞)順序 IO(好)
讀取直接 log(N)可能要看多層
空間放大小(in-place)大(多版本暫存)
寫放大較大(compaction)
適合介質HDD / SSD 都行為 SSD 設計(順序寫快)
全表掃描慢(遍歷葉子)快(每層連續)

對「寫多讀少 + SSD」場景,LSM 完勝。

LSM-Tree 結構#

        memtable           (memory,小,新寫入在這)
            ↓ flush
       Level 0:file 1, file 2, ...    (重疊 key 範圍)
            ↓ compact
       Level 1:file 1, file 2, ...    (key 範圍不重疊)
            ↓ compact
       Level 2:...
            ↓ ...
       Level N:(最大、最舊)

寫入流程#

1. WAL 寫一條 log(崩潰恢復用)
2. 寫進 memtable(記憶體跳表 / B-tree)
3. memtable 滿 → flush 成 Level 0 SSTable(不可變檔案)
4. 後台 compaction 把 Level 0 合進 Level 1,Level 1 合進 Level 2,...

寫操作只在記憶體 + 順序磁碟寫 ── 極快。

讀取流程#

1. 查 memtable
2. 查 Level 0 各 SSTable(可能多個有此 key)
3. 查 Level 1(用 key 範圍 prune)
4. 查 Level 2、...
5. 找到第一個(最新版本)回傳

最壞情況要查 N 層 + Level 0 多檔案。RocksDB 用 bloom filter 跳過大部分查找。

Compaction:後台魔法#

LSM 性能的關鍵。把多個小檔案合併成大檔案:

  • 合並重複 key(保留最新)
  • 應用 tombstone(真正刪除)
  • 壓縮(lz4 / zstd)
  • key 範圍重新整理

兩種 compaction 策略:

Leveled compaction#

每 level 大小是上一層 N 倍。Level i 與 Level i+1 部分檔案合併到 Level i+1。

L0  ─→ L1(合併重疊 key)
L1  ─→ L2
...

每層大小:L1 = 256 MB,L2 = 2.56 GB,L3 = 25.6 GB ...

優點:讀放大小、空間放大小(< 1.1x)。 缺點:寫放大大(每層都要寫一次)。

預設 RocksDB Leveled。

Tiered / Universal compaction#

每層多個 SSTable,達數量門檻就 merge 成下層一個大 SSTable。

優點:寫放大小。 缺點:讀放大大(多 SSTable 要查)、空間放大可能 2x+。

Cassandra 用 SizeTieredCompactionStrategy 是這個。

三大放大#

LSM-Tree 的權衡核心:

放大含義
寫放大(WA)每寫 1 byte 用戶資料,磁碟實際寫入幾 byte
讀放大(RA)每讀 1 byte 用戶資料,磁碟實際讀取幾 byte
空間放大(SA)用戶資料 vs 磁碟占用

寫密集場景關注 WA(直接影響吞吐)。讀密集關注 RA。容量緊張關注 SA。

LSM-Tree 通常 WA 1030、RA 110、SA 1.1~2。沒有完美 ── 調參數調的就是這三個的取捨。

RocksDB 的關鍵特性#

1. Column Families#

類似 SQL 的 schema:

DB* db = DB::Open(...);
db->CreateColumnFamily("orders", ...);
db->CreateColumnFamily("inventory", ...);

db->Put(orders_cf, "key", "value");
db->Get(inventory_cf, "key", &value);

每個 CF 獨立的 memtable + LSM。可以為每個 CF 設不同 compaction 策略、壓縮、快取策略。

2. WriteBatch#

批次寫入:

WriteBatch batch;
batch.Put("k1", "v1");
batch.Put("k2", "v2");
batch.Delete("k3");
db->Write(write_options, &batch);

批次原子(要嘛全成功要嘛全失敗)+ 性能比逐筆好。

3. Snapshot 與 MVCC#

const Snapshot* snap = db->GetSnapshot();
ReadOptions ropts;
ropts.snapshot = snap;
db->Get(ropts, "key", &value);   // 讀 snapshot 時間版本
db->ReleaseSnapshot(snap);

multiversion concurrency。配合 NewSQL 的事務語意。

4. Bloom filter#

每個 SSTable 內建 bloom filter,跳過大部分「key 一定不在」的檔案。對「不存在的 key」查詢效能極佳。

5. Block cache#

讀過的 block 快取在記憶體(LRU)。RocksDB 預設用 8 MB block size、LRU cache 32 MB(生產環境調幾 GB ~ 幾十 GB)。

6. Compaction filter#

在 compaction 時自定 callback,可以根據業務邏輯刪除 key(例:TTL):

class TTLCompactionFilter : public CompactionFilter {
    bool Filter(int level, const Slice& key, const Slice& existing_value, ...) {
        if (is_expired(existing_value)) return true;  // drop
        return false;  // keep
    }
};

適合的工作負載#

適合#

  • 寫多讀少(log、metric、event)
  • 高並發 KV 訪問
  • 範圍掃描(同 prefix 連續)
  • SSD 部署
  • 大量小 KV(每筆 100 byte ~ KB 級)

不適合#

  • 高頻 random read(RA 高)
  • 大 value(每筆幾 MB+)── 用 BlobDB 變體
  • 強事務跨多 key(要外加層)
  • 主要 OLAP 工作負載 ── 用列式

與其他 KV 比較#

系統結構特色
RocksDBLSM-Tree嵌入式、高度可調
LevelDBLSM-Tree(簡)RocksDB 的祖先、單執行緒 compaction
BadgerDBLSM-TreeGo 寫,分離 key 與 value
LMDBB+ Treemmap、單寫多讀、極簡
BoltDBB+ TreeLMDB 的 Go 移植
InnoDBB+ TreeMySQL 內建

電商日誌、訂單、metric 用 LSM 系。設定、user profile 用 B+ Tree 系。

RocksDB 調優入門#

最常碰的幾個參數:

options.write_buffer_size = 64 << 20;        // memtable 大小,64MB
options.max_write_buffer_number = 4;         // memtable 個數
options.target_file_size_base = 64 << 20;    // L1 SSTable 大小
options.max_bytes_for_level_base = 256 << 20;// L1 總大小
options.compression = kSnappyCompression;    // 壓縮
options.level_compaction_dynamic_level_bytes = true;  // 自動調整每層大小

對寫密集場景關鍵:

  • write_buffer_size 大 → 攢更多 → flush 更少 → 寫放大小
  • max_background_jobs 大 → 多核 compaction 並行
  • compression_per_level:頂層用 lz4(快)、底層用 zstd(壓縮高)

一個觀察#

LSM-Tree 為什麼 2010 後爆發?因為:

  • SSD 普及(順序寫遠比 random 寫便宜)
  • 寫密集應用增多(log、metric、events)
  • 雲端按 IOPS 計費,LSM 寫放大 trade-off 變得重要

過去 InnoDB 統治 OLTP,現在 OLTP(NewSQL)+ NoSQL + 訊息中介 + 監控全部基於 LSM。LSM 是過去 15 年最重要的儲存創新

進一步閱讀#

  • 論文:The Log-Structured Merge-Tree(O’Neil et al., 1996)── LSM 原論文
  • RocksDB Wiki:詳細 tuning guide
  • Designing Data-Intensive Applications(Martin Kleppmann)── 第 3 章
  • LevelDB 原始碼(~10K 行)── 想學 LSM 內部最好起點

小結#

  • LSM-Tree:append + 後台 compaction,為 SSD + 寫密集設計
  • 三大放大:寫放大、讀放大、空間放大 ── 沒有完美設計
  • RocksDB:Facebook 工業級 LSM 引擎,幾乎所有現代分散式 KV 內部
  • compaction 策略:Leveled(讀好寫差)、Tiered(寫好讀差)
  • column families、snapshot、bloom filter、block cache 是 RocksDB 的關鍵
  • 不只 KV ── NewSQL、訊息佇列、監控、流處理全都疊在 LSM 之上

15 章到此結束。從訂單冪等到 LSM-Tree,整條電商儲存架構的演化路徑串完。

下個方向值得讀:跨地域強一致(Spanner 論文)、HTAP 細節(TiDB 論文)、向量資料庫(語意搜尋的下一步)、AI 訓練資料管道。