RocksDB 為什麼重要#
很少人直接用 RocksDB,但它在「現代儲存底層」幾乎無處不在:
| 系統 | 用 RocksDB 做什麼 |
|---|---|
| MyRocks(Facebook MySQL) | InnoDB 的替代儲存引擎 |
| TiDB / TiKV | 每個 region 的本地儲存 |
| CockroachDB | 每個 range 的本地儲存 |
| Kafka Streams | state store |
| Flink | RocksDB state backend |
| Cassandra(部分) | memtable 後端 |
| Druid | segment storage |
| Ceph | metadata 儲存 |
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+ Tree | LSM-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 比較#
| 系統 | 結構 | 特色 |
|---|---|---|
| RocksDB | LSM-Tree | 嵌入式、高度可調 |
| LevelDB | LSM-Tree(簡) | RocksDB 的祖先、單執行緒 compaction |
| BadgerDB | LSM-Tree | Go 寫,分離 key 與 value |
| LMDB | B+ Tree | mmap、單寫多讀、極簡 |
| BoltDB | B+ Tree | LMDB 的 Go 移植 |
| InnoDB | B+ Tree | MySQL 內建 |
電商日誌、訂單、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 訓練資料管道。