01

總覽與定位

嵌入式資料庫解決什麼痛?與 Client-Server DB、分散式 SQL 在系統設計版圖中的座標。

當你的服務跑在行動 App、IoT 閘道器、邊緣節點、或單機 Daemon上,往往無法(也不該)每次都連遠端 PostgreSQL——你需要一個跟著程序走、零網路依賴的儲存層。嵌入式資料庫(Embedded Database)把引擎打包成 library,以 in-process 方式被應用直接連結,資料通常落在本地檔案或記憶體中。

但「嵌入式」底下其實有兩條截然不同的產品線,面試與架構決策時必須分清楚:

  • 嵌入式關聯型 / 應用直連(以 SQLite 為代表):開發者直接寫 SQL、拿 ACID transaction,像用小型 PostgreSQL 一樣。
  • 嵌入式儲存引擎 / 被上層系統當底層(以 RocksDB 為代表):不提供完整 SQL,而是 Key-Value + LSM-Tree 寫入路徑,被 Kafka、Flink、MySQL/Rocks 等當 storage engine。
座標Embedded DB vs Client-Server vs Distributed SQL

• Client-Server DB(PG / MySQL):獨立程序、網路協定、多 client 共享,適合多租戶後端。
• Embedded DB(SQLite / RocksDB):in-process、低延遲、離線可用,適合 edge / 單機 / 被其他 DB 嵌入。
• Distributed SQL(Cockroach / TiDB):水平擴展 + SQL,與嵌入式場景互斥——不是「升級版 SQLite」,而是不同問題域。
實務常見:Mobile App → SQLite、Stream Processor state → RocksDB、後端 SoT → PostgreSQL。

維度 SQLite RocksDB PostgreSQL
定位 應用直連的嵌入式 RDBMS 嵌入式 KV 儲存引擎 Client-Server RDBMS
API SQL + prepared statement Get / Put / Iterator SQL over wire
儲存結構 B-Tree(單檔 .db) LSM-Tree(SST + WAL) B-Tree / 可插拔 engine
並發模型 DB 級寫鎖、WAL 模式可多讀 Column Family 並行寫 MVCC + 連線池
典型場景 手機離線快取、桌面 App、邊緣閘道 Kafka log、Flink state、MyRocks 後端 SoT、多租戶 SaaS

面試高分:能說清楚「SQLite 給應用寫 SQL」與「RocksDB 給系統當 engine」是兩種嵌入式,不是同一類產品的競品。

一句話答法嵌入式 DB 分兩派——SQLite 是 in-process 關聯型給 App 直連;RocksDB 是 LSM-Tree KV engine 給 Kafka / Flink / MySQL 當底層,兩者互補而非互斥。

先備兩個反覆出現的底層原語:WAL 與 LSM-Tree

接下來每一節、連互動 Demo 都會不斷用到這兩個詞。它們不專屬某個引擎,而是儲存系統的通用構件,先各給一句定義與一張流程小圖:

WAL(Write-Ahead Log)——跨資料庫的通用耐久性原語:改資料前,先把「打算做什麼」循序 append 進一份 log、確認落盤後才動主結構。好處是 crash 後能用 log replay 補回未落盤的寫入、或 rollback 未提交的交易,不留半殘狀態;同時把大量隨機寫收斂成一次循序寫。SQLite、RocksDB、PostgreSQL 都靠它保證 crash consistency——下文 SQLite 的「WAL 模式」與 RocksDB 的「WAL append」都是同一概念的應用。

寫入 ① append log → ② fsync 落盤 → ③ 更新主結構
crash 後 ↩ 讀 log:replay 補回 / rollback 回滾

LSM-Tree(Log-Structured Merge-Tree)——一種只追加、不原地改的儲存結構。B-Tree 是定位頁面後原地覆寫(隨機寫);LSM 反過來——新寫入一律堆進記憶體 MemTable、批次 flush 成不可變的 SST 檔,資料因此分成新舊多層,再靠背景 compaction 慢慢合併、丟棄被覆蓋或刪除的舊值。核心取捨:以寫入放大+需要背景 compaction 為代價,把隨機寫轉成循序寫,換得極高寫入吞吐——這正是 Kafka/Flink 這類寫密集元件選它的原因。

B-Tree   :定位頁面 → 原地覆寫(隨機寫)
LSM-Tree :寫入 → MemTable → flush → L0 → compaction → L1(順序寫)

02

SQLite:嵌入式關聯型,直接給應用用

單檔、零部署、ACID——行動與邊緣場景的預設選項,但並發寫入與多程序共享有硬上限。

SQLite 是全球部署量最大的資料庫之一(幾乎每支手機都有)。它把整個 DB 存成單一 .db 檔,引擎以 library 形式連結進 App process,查詢走 B-Tree 頁面結構,支援完整 SQL、索引、transaction、foreign key。

為什麼 Edge / Mobile 愛 SQLite?

  • 零運維:不需起 DB server、不需 port、不需帳密輪替。
  • 離線優先:地鐵、工廠內網斷線時仍可讀寫本地 SoT,恢復連線再 sync 上雲。
  • 可預測延遲:in-process 呼叫,無 network RTT;簡單 point lookup 微秒~毫秒級。
  • ACID 語意完整:BEGIN … COMMIT 真正 rollback,適合本地業務狀態機。
-- 典型 Android / iOS / Electron 本地快取
CREATE TABLE sync_queue (
  id INTEGER PRIMARY KEY,
  payload TEXT NOT NULL,
  status TEXT DEFAULT 'pending',
  updated_at INTEGER
);
CREATE INDEX idx_status ON sync_queue(status);

BEGIN IMMEDIATE;
UPDATE sync_queue SET status = 'syncing' WHERE id = ?;
-- 上傳成功後 COMMIT,失敗 ROLLBACK 恢復 pending
COMMIT;

並發與 WAL 模式

預設 rollback journal 下,SQLite 同一時間只允許一個 writer(DB 級鎖)。開啟 WAL(Write-Ahead Log) 後可多讀單寫:reader 讀舊 snapshot,writer append WAL,適合讀多寫少的本地 cache。

陷阱把 SQLite 當多寫入後端 DB

十幾個微服務同時寫同一個 .db 檔——即使 WAL 也會在 writer 鎖上排隊,P99 飆升。SQLite 設計假設是單 process 或少量協調寫入;高並發寫入請用 Client-Server DB,SQLite 只當 edge replica / cache。

實務Edge 同步模式

1. 本地 SQLite 為離線 SoT(使用者操作立即落盤)。
2. Background worker 批次 push 到雲端 API(PostgreSQL SoT)。
3. Conflict resolution:LWW(Last-Write-Wins)、CRDT、或 server authoritative merge。
4. 注意 PRAGMA synchronous / journal_mode 與裝置儲存速度的平衡。

設定 效果 取捨
journal_mode=WAL 多讀單寫、寫入不阻塞讀 需額外 -wal / -shm 檔;備份需 checkpoint
synchronous=NORMAL 平衡 durability 與速度 極端斷電可能丟最後一個 transaction
synchronous=FULL 每次 commit fsync 慢但最安全;金融級本地帳本可考慮
一句話答法SQLite = 單檔 in-process SQL + ACID,適合 mobile / edge 離線 SoT;WAL 改善讀並發,但多 writer 場景仍應換 Client-Server DB。

03

RocksDB:嵌入式 LSM 儲存引擎,被系統當底層

不提供 SQL——以 LSM-Tree 優化寫入吞吐,是 Kafka、Flink、MyRocks 等元件的 storage substrate。

RocksDB 源自 Facebook 對 LevelDB 的強化,是嵌入式 Key-Value store,底層採 LSM-Tree(Log-Structured Merge-Tree):寫入先進 MemTable(記憶體),滿了 flush 成不可變 SST 檔,背景 compaction 合併層級。這與 SQLite 的 B-Tree「原地更新頁面」路徑截然不同——寫入放大換讀取與寫入吞吐。

LSM 寫入路徑(呼應 LSM-Tree 概念)

  1. WAL append:先寫順序 log 保 durability,crash 可 replay。
  2. MemTable insert:記憶體內 sorted structure(often skiplist),寫入 O(log n) 且 sequential I/O 友善。
  3. Flush → L0 SST:MemTable 滿了 immutable 並 flush 成 SST 檔。
  4. Compaction:背景 merge L0→L1→… 移除 tombstone、降低 read amplification。
關鍵RocksDB 不是給 App 開發者寫 CRUD 的

你通常不會在產品 code 裡直接 db.Put("user:1", …) 當主 DB——而是基礎設施團隊把 RocksDB 嵌進:
• Apache Kafka:broker local log segments(新版 tiered storage 亦相關)
• Apache Flink:RocksDBStateBackend 存 checkpoint / keyed state
• MySQL + MyRocks:用 Rocks 取代 InnoDB 作 storage engine
• TiKV / Cockroach 等亦受 LSM 思路影響

Column Family 與 Tune 旋鈕

RocksDB 用 Column Family(CF) 在同一 DB 實例邏輯分割 keyspace,各自 compaction 策略。Tune 重點:

參數 / 概念 影響 面試怎麼講
write_buffer_size MemTable 大小 → flush 頻率 大 buffer 減 flush 次數但增 memory
compaction_style (level / universal) 合併策略與 write amp 寫多選 level compaction 常見;讀延遲敏感要顧 L0 檔數
max_background_jobs compaction 並行度 compaction 跟不上 → L0 堆積 → read amp 爆炸
Tombstone / TTL 刪除標記需 compaction 才真正清 「刪了 key 為何還佔空間?」→ 等 compaction

那讀取怎麼辦?一個 key 可能散落在 MemTable 與多層 SST,理論上每次 GET 都要逐層探查(read amplification)。RocksDB 靠兩個機制壓制:每個 SST 內建 Bloom filter——先用機率結構判斷「這個 SST 絕對不含該 key」就直接跳過,只探真正可能命中的檔;再加上 block cache 把熱門資料頁留在記憶體。因此實務 read amp 遠低於「L0 檔數」的最壞估計。

陷阱Compaction 跟不上寫入

L0 SST 堆太多檔案,每次 read 要查多個 SST(read amplification↑),P99 延遲惡化。Flink checkpoint 密集寫入 + 小 MemTable + 少 background job 是典型踩雷組合——需監控 pending_compaction_bytes 與 stall 次數。

一句話答法RocksDB 是 LSM-Tree 嵌入式 KV engine,給 Kafka / Flink / MyRocks 當底層;寫入走 WAL→MemTable→SST→Compaction,Tune 重點是 compaction 與 write/read amplification 平衡。

04

SQLite vs RocksDB:怎麼選、怎麼共存

一個回答「App 怎麼存資料」,一個回答「系統元件怎麼持久化 state」——決策樹與共存架構。

決策樹

  • 需要 SQL、JOIN、離線 App cache → SQLite
  • 你在寫 DB / MQ / Stream Processor 本身,要高寫入 KV + 可 tune LSM → RocksDB
  • 需要 多 client 高並發寫入、跨機 HA → 都不是嵌入式答案,上 PostgreSQL / Distributed SQL
Trade-off SQLite RocksDB
開發者體驗 SQL 生態成熟 需懂 KV + LSM tune
寫入模式 B-Tree 原地更新,隨機寫 LSM 順序寫友好,寫吞吐高
讀取模式 索引 point lookup 快 Range scan 需跨 SST,受 compaction 影響
部署形態 單檔複製即備份 目錄樹 + SST,需理解 live file set
誰來維護 App 工程師 平台 / 基礎設施工程師
實務同一產品裡兩者共存

智慧工廠 Edge Gateway:SQLite 存本地感測器採樣與離線告警佇列(App 層 SQL);閘道器上跑的輕量 stream processor 視窗聚合 state 用 RocksDB(元件內嵌 engine)。
雲端 SoT 仍是 PostgreSQL——嵌入式只負責 edge 低延遲與離線,不做全局一致性的唯一來源。

陷阱用 RocksDB 重新發明 SQL

在 RocksDB 上自己實作 secondary index、transaction、query planner——除非你在做資料庫公司,否則應直接用 SQLite(要 SQL)或 PostgreSQL(要 server)。RocksDB 的價值是當 engine 被嵌入,不是當 App ORM 的替代品。

一句話答法App 要 SQL 離線 → SQLite;基礎設施要高寫入 KV engine → RocksDB;兩者可共存於 edge 不同層,全局 SoT 仍應在 Client-Server DB。

05

互動 Demo:同一批寫入,B-Tree 與 LSM 怎麼演化

左右並排餵同一批寫入,直接對比 SQLite 的 B-Tree 原地更新+WAL 與 RocksDB 的 MemTable→L0→L1 分層寫入與 compaction。

下方兩欄接收同一批寫入請求。左邊 SQLite 走 B-Tree:每筆寫入原地翻新某個資料頁、append WAL,累積到門檻自動 checkpoint。右邊 RocksDB 走 LSM:寫入先進 MemTable,滿了 flush 成一塊 L0 SST,L0 堆太多得靠 compaction 併進 L1。按 GET 觀察兩者讀取路徑差異——B-Tree 一次命中 vs LSM 靠 Bloom filter 跳過不含該 key 的 SST。

互動 · 同一批寫入,兩種引擎 寫入 / 查詢同時餵給兩邊,對比儲存結構如何演化
寫入總數:0
SQLite · B-Tree + WAL(原地更新)
WAL(append,滿了 checkpoint)
B-Tree 資料頁(寫入原地翻新,橘=剛更新)
RocksDB · LSM-Tree(追加+合併)
MemTable(記憶體,滿了 flush)
L0 SST(flush 產物,key 範圍可重疊)
L1 SST(compaction 合併後)
空槽 / 閒置 WAL / Flush 寫入中 / MemTable 已落盤(頁 / SST) MemTable 滿 / Stall GET 命中 Bloom 跳過
按「寫入 1 筆」或「批次寫入 ×5」,同一批寫入會同時餵給 SQLite 與 RocksDB;再按「GET 查一筆」對比讀取路徑。

Demo 簡化真實引擎內部:SQLite 頁面數、WAL 門檻與 RocksDB 的 MemTable 容量、層數都縮小到方便觀察的量級,重點在兩種寫入 / 讀取路徑的思維模型對比。

一句話答法同一批寫入下,SQLite 原地翻新 B-Tree 頁面、WAL 累積後 checkpoint(read amp 恆為 1,但受單 writer 限制);RocksDB 把寫入堆進 MemTable→flush L0→compaction 併 L1(寫入吞吐高,但 L0 堆積會 stall),讀取靠 Bloom filter 跳過不含該 key 的 SST。

06

面試考點與架構折衷

Edge 離線同步怎麼設計?Flink 為何預設 RocksDB state?何時 SQLite 不該硬撐?

高頻考點

考點 1「Mobile App 本地資料怎麼存?」

回答框架:
1. 離線 SoT 用 SQLite(ACID、單檔、WAL 多讀)。
2. 同步策略:queue + batch upload、conflict resolution(LWW / server wins)。
3. 敏感資料:SQLCipher 或 OS Keychain 加密 key。
4. 不要多 service 寫同一 .db;全局一致性交給雲端 PG。

考點 2「Flink 為什麼用 RocksDB 做 state?」

Keyed state 可能大於 heap,需 spill 到本地 disk。RocksDB LSM 寫入吞吐高、支援 iterator scan window state,且 embedded 無需外部 DB 運維。代價是 checkpoint 時需 snapshot SST + 上傳 DFS,compaction 與 disk I/O 要 tune。

考點 3「SQLite 和 RocksDB 差在哪?」

• 抽象層級:SQLite = SQL RDBMS for apps;RocksDB = KV engine for systems。
• 資料結構:B-Tree vs LSM-Tree。
• 並發:SQLite 單 writer;RocksDB 多 CF 並行寫但無 SQL transaction 跨行語意。
• 運維:SQLite 拷貝 .db 即備份;RocksDB 需理解 SST + checkpoint + compaction backlog。

陷阱Edge 只談 SQLite 不談 sync

面試官追問「斷網三天後上線怎麼合併?」——必須講 version vector / logical clock / server authoritative merge,否則本地 ACID 無法延伸到分散式一致性。

場景 推薦 避免
IoT 閘道離線快取 SQLite + 雲端 PG sync 嵌入式當唯一 global SoT
流處理大 state RocksDBStateBackend 全放 heap 導致 OOM
微服務共享寫入 PostgreSQL / Redis 多 process 爭用 SQLite 檔案鎖
自研資料庫 storage 層 RocksDB / 自研 LSM SQLite 當底層 engine
一句話答法Edge 用 SQLite 做離線 SQL SoT 並設計 sync;流處理與大 state 用 RocksDB embedded LSM;兩者服務不同層級,全局 HA 仍靠 Client-Server / Distributed DB。