持久化
了解 ToriiDB 如何結合記憶體狀態、append-only log、逐鍵檔案、重播、壓縮與過期清理。
儲存配置
儲存根目錄會為每個邏輯資料庫建立一個目錄:
<root>/
└── db_0/
├── record.aof
└── xx/yy/zz/<md5>.json
預設根目錄為 ./temp;store.New(path) 可指定其他根目錄。啟動時會建立 16 個資料庫 descriptor,但個別資料庫目錄與 AOF handle 採延遲初始化。
記憶體狀態
每個資料庫都有一份由 sync.RWMutex 保護的 map[string]*Entry,它是執行期的主要真實來源。Entry 包含 value、偵測型別、timestamp、選用 expiration、選用 vector 與 parsed JSON cache。
資料庫彼此獨立;切換資料庫會改變 session 使用的 map 與 persistence directory。
逐鍵 JSON 檔案
每次寫入都會把完整 entry 儲存成 JSON file。路徑由 key 的 MD5 hash 推導,再拆成三層、每層兩個字元的目錄:
<root>/db_N/ab/cd/ef/abcdef....json
utils.WriteFile 會建立 parent directory、寫入同層 temporary file,再 rename 覆蓋 target。Entry writer 使用 Entry.JSON(),不直接 marshal Entry,以確保包含未匯出的 raw value。
刪除 key 時會移除 entry file;週期性過期清理也會移除對應檔案。
Append-Only Log
record.aof 每行包含一個 JSON object。每筆 AOFRecord 可含:
| 欄位 | 意義 |
|---|---|
ts |
操作的 Unix timestamp |
cmd |
SET、DEL、EXPIRE、EXPIREAT 或 PERSIST |
key |
受影響 key |
value |
SET 記錄的 value |
expire_at |
選用的絕對過期 timestamp |
vector |
選用的 base64 little-endian float32 vector |
每次追加後都會執行 File.Sync。寫入 handler 通常在持有資料庫寫鎖時,依序更新記憶體、逐鍵檔案與 AOF。
重播
首次存取資料庫時只會呼叫一次 replayAOF。重播流程:
- 讀取逐行記錄。
- 忽略空白或格式錯誤的行。
- 重建
SETvalue 與 vector。 - 套用 deletion 與 expiration operation。
- 為 JSON value 預熱 parsed cache。
- 將有效 log size 記為 compaction baseline。
若 AOF 不存在,資料庫會從空狀態開始。逐鍵 JSON file 是 cache artifact;啟動重建以 AOF 為準。
壓縮
目前 AOF size 達到以下兩者較大值的兩倍時,自動觸發壓縮:
- 載入後或上次壓縮建立的 AOF size baseline。
- 1 MiB。
壓縮會為每個未過期 entry 寫出一筆目前的 SET 記錄,包含 expiration 與 vector,並捨棄已被取代的歷史。若沒有 entry,則移除 AOF。
Store.Close() 會等待背景向量工作、停止清理,再並行壓縮所有已載入資料庫。Graceful shutdown 時應呼叫它。
過期清理
過期會在多個層級強制套用:
| 路徑 | 行為 |
|---|---|
| 點讀取 | 將過期 entry 視為 missing,並可能從記憶體移除 |
| 掃描操作 | 略過過期 entry |
| 清理計時器 | 每分鐘從已載入資料庫移除過期 entry 與 file |
| 壓縮 | 重寫 AOF 時排除過期 entry |
因此 key 在 expiration timestamp 到達時即為邏輯不存在,不必等待實體檔案刪除。
備份與還原
- 備份完整 storage root,不要只備份
record.aof。 - 需要一致的 point-in-time state 時,應先停止寫入或關閉 store,再建立 filesystem snapshot。
- 保留 directory permission 與所有 database subdirectory。
- AOF 足以供 runtime replay;entry file 則提供逐鍵持久化表示。
- 維持穩定 embedding dimension,使還原後 vector 仍可搜尋。