Log-Structured Merge-Tree,簡稱 LSM。
以 Mysql、postgresql 為代表的傳統 RDBMS 都是基於 b-tree 的 page-orented 儲存引擎。現代計算機的最大處理瓶頸在磁碟的讀寫上,資料儲存無法繞開磁碟的讀寫,純記憶體型資料庫除外,但由於記憶體儲存的不穩定性,我們一般只將記憶體型的儲存作為快取系統。
為提升資料庫系統的寫效能,我們發現磁碟的順序寫效能遠遠大於隨機寫效能,甚至效能高於記憶體的隨機寫。所以在很多偏向寫效能的資料庫系統中,以犧牲一部分讀效能和增大寫放大的情況下引入了 LSM 資料結構。
設計一個資料庫引擎
我們從頭開始設計一個資料庫引擎。資料模型很簡單,我們選最簡單的 Key-Value 結構,一條資料只有一個 Key 和一個 Value。操作只有 get 和 put,如下:
get(key);
put(key, value);
從最簡單的開始,每個資料庫一個data.db
檔案,我們像寫日誌一樣,將每條記錄 append 到檔案結尾。
key1,value1
key2,value2
key3,value3
key10,value10
key8,value8
這樣我們已經完成了 80%了,然後需要完成讀功能。如上資料檔案,若需要查詢 key2 資料,我們只能從檔案開頭開始遍歷,當直到讀取到 key2 資料:
for (row in rows) {
if (row.key == "key1") {
return row;
}
}
好了,一個簡單的資料庫就完成了。
什麼?完成了?是的,完成了,雖然說拿出去會被砍死,但誰也不能否認它已經完成了一個資料庫系統的最基本功能。
這樣的遍歷是十分耗費效能的。那麼怎麼提高讀取效能呢?建立一個記憶體索引“Index”即可,最簡單的方式,在記憶體中維護一個 Map,儲存每個 key 對應的檔案內容偏移量。這樣讀取一條記錄就只需要一次記憶體操作加上一次磁碟操作就可以了。
b-tree 是因何出現的?想一想上面的 Map 結構的索引有什麼缺點?Map 索引解決了隨機單點讀的效能問題,但無法解決 Rang 查詢,比如需要查詢 key 在 key1 和 key200 之間的資料。於是,就有了 b-tree,b 樹是有序的結構樹,可以很簡單的進行 Rang 查詢。
b-tree 將所有資料都索引在記憶體中,當資料無限增長時,將無法在記憶體中存放這麼大的索引檔案。
我們來看看 LSM 的實現。
LSM 架構
SSTable:LSM 的磁碟檔案,稱作SSTable(Sorted String Table)。望文得意,LSM 儲存在磁碟中的檔案,資料也是按 Key 排序儲存的,這樣就可以解決上面講到的資料量大了之後無法將資料全部索引到記憶體中的問題。如果磁碟檔案也是有序的,那麼記憶體索引可以採取”稀疏索引“(Sparse Index),可以每一段記錄一個索引,將資料邏輯上分成多個block
,稀疏索引只需要記錄每個block
的偏移量,每條資料通過遍歷block
實現。這樣索引量將大大減小。
Memtable:LSM 的記憶體結構叫做Memtable。Memtable是一個有序結構,同樣可以採用樹結構,可以用跳錶
。LSM 寫資料時,只需要寫入記憶體中的Memtable,當Memtable到達一定量之後,會非同步刷入磁碟,就是上面的SSTable。
immutable Memtable:在資料從記憶體Memtable刷入SSTable時,為避免讀寫鎖導致的效能問題,LSM 會在記憶體中 copy 一份immutable Memtable表,顧名思義,這個資料結構不可改變,新寫入的資料只會寫入新的Memtable,immutable Memtable供刷盤執行緒讀取,查詢資料的請求也可以訪問這個資料結構,這樣如果資料在記憶體中,就不需要訪問磁碟,可以提供資料查詢的效率。
WAL:write ahead log,預寫日誌,關於 WAL,可以參考我之前的文章《你常聽說的 WAL 到底是什麼》。在 LSM 中,在資料刷入磁碟前,為防止異常導致資料丟失,LSM 會先將資料寫入 WAL,然後寫入 SSTable,系統重啟時,LSM 會從 WAL 中回溯 SSTable,當寫完一個 SSTable 時,LSM 會清理掉過期的 WAL 日誌,防止 WAL 過量。
LSM 寫
LSM 的寫包括四個流程:
- 寫入 WAL
- 寫入 memtable
- memtable 達到閾值時,複製 imutable memtable
- 非同步刷入磁碟
LSM 刪除
為保證順序寫磁碟,LSM 不會去直接刪除資料,而是通過寫一條 delete 標識來表示資料被刪除,資料只有在被 Compact 時才會被真正刪除。
LSM 讀
LSM 讀取資料將從memtable、imutable、sstable依次讀取,直到讀取到資料或讀完所有層次的資料結構返回無資料。所以當資料不存在時,需要依次讀取各層檔案。LSM 可以通過引入布隆過濾器來先判斷一個資料是否存在,避免無效的掃檔案。
LSM 合併
LSM 的合併策略是 LSM 很重要的一個部分,我們將放在下一篇文章中單獨講解。
LSM 結構的應用十分廣泛,諸如Bigtable,HBase,LevelDB,SQLite4, Tarantool , RocksDB,WiredTiger ,Apache Cassandra,InfluxDB 底層都使用了 LSM。只好的文章,我們將詳細講解 LSM 在 leveldb 或 Cassandra 中的實現。
推薦:
Mysql 大表問題和解決
Mysql 主鍵問題
列式儲存
時間序列資料庫(TSDB)初識與選擇
十分鐘瞭解 Apache Druid
Apache Druid 底層儲存設計
Apache Druid 的叢集設計與工作流程