LSM設計一個數據庫引擎


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 的內存結構叫做MemtableMemtable是一個有序結構,同樣可以采用樹結構,可以用跳表。LSM 寫數據時,只需要寫入內存中的Memtable,當Memtable到達一定量之后,會異步刷入磁盤,就是上面的SSTable

immutable Memtable:在數據從內存Memtable刷入SSTable時,為避免讀寫鎖導致的性能問題,LSM 會在內存中 copy 一份immutable Memtable表,顧名思義,這個數據結構不可改變,新寫入的數據只會寫入新的Memtableimmutable Memtable供刷盤線程讀取,查詢數據的請求也可以訪問這個數據結構,這樣如果數據在內存中,就不需要訪問磁盤,可以提供數據查詢的效率。

WAL:write ahead log,預寫日志,關於 WAL,可以參考我之前的文章《你常聽說的 WAL 到底是什么》。在 LSM 中,在數據刷入磁盤前,為防止異常導致數據丟失,LSM 會先將數據寫入 WAL,然后寫入 SSTable,系統重啟時,LSM 會從 WAL 中回溯 SSTable,當寫完一個 SSTable 時,LSM 會清理掉過期的 WAL 日志,防止 WAL 過量。

LSM 寫

LSM 的寫包括四個流程:

  1. 寫入 WAL
  2. 寫入 memtable
  3. memtable 達到閾值時,復制 imutable memtable
  4. 異步刷入磁盤

LSM 刪除

為保證順序寫磁盤,LSM 不會去直接刪除數據,而是通過寫一條 delete 標識來表示數據被刪除,數據只有在被 Compact 時才會被真正刪除。

LSM 讀

LSM 讀取數據將從memtableimutablesstable依次讀取,直到讀取到數據或讀完所有層次的數據結構返回無數據。所以當數據不存在時,需要依次讀取各層文件。LSM 可以通過引入布隆過濾器來先判斷一個數據是否存在,避免無效的掃文件。

LSM 合並

LSM 的合並策略是 LSM 很重要的一個部分,我們將放在下一篇文章中單獨講解。

LSM 結構的應用十分廣泛,諸如BigtableHBaseLevelDBSQLite4, Tarantool RocksDBWiredTiger Apache CassandraInfluxDB 底層都使用了 LSM。只好的文章,我們將詳細講解 LSM 在 leveldb 或 Cassandra 中的實現。

推薦:

Mysql 大表問題和解決
Mysql 主鍵問題
列式存儲
時間序列數據庫(TSDB)初識與選擇
十分鍾了解 Apache Druid
Apache Druid 底層存儲設計
Apache Druid 的集群設計與工作流程

碼哥字節


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM