簡述LSM-Tree


LSM-Tree

1. 什么是LSM-Tree

LSM-Tree 即 Log Structrued Merge Tree,這是一種分層有序,硬盤友好的數據結構。核心思想是利用磁盤順序寫性能遠高於隨機寫。

LSM-Tree 並不是一種嚴格的樹結構,而是一種內存+磁盤的多層存儲結構。HBase、LevelDB、RocksDB這些 NoSQL 存儲都使用了 LSM-Tree。

2. LSM的組成部分

image-20220226111632073

2.1 MemTable

MemTable 是 LSM-Tree 在內存中的數據結構,只用於保存最新的數據,按照 Key 有序地組織這些數據。

LSM-Tree 沒有規定用怎樣的數據結構實現 MemTable,例如 HBase 使用跳表來保證內存中 Key 的有序性。

存在內存中的數據會因為斷電丟失,所以我們通常使用 WAL,即預寫日志的方式來保證數據的可靠。

WAL:預寫日志,即事務的所有修改在提交之前要先寫入 log 文件中

2.2 Immutable MemTable

MemTable 達到一定大小后,會轉化為 Immutable MemTable。Immutable MemTable 是將 MemTable 轉為磁盤上的 SSTable 的一種中間狀態。

轉化過程中寫操作由新的 MemTable 處理,過程中不阻塞數據更新操作。

2.3 SSTable

SSTable 是有序鍵值對集合,是 LSM 樹在磁盤中的數據結構。它是一種持久化、有序且不可變的鍵值村存儲結構

SSTable 內部包含一系列可配置大小的 Block 塊。這些 Block 的 index 會被存儲在 SSTable 的尾部,用於幫助快速查找特定的 Block。當一個 SSTable 被打開時,index 表會被加載到內存,然后根據 key 在內存 index 中進行一個二分查找,查到該 key 對應的磁盤的 offset 后,去磁盤把響應的塊數據讀取出來。

image-20220226112453122

當然,如果內存足夠大,可以直接利用 MMAP 的技術把 SSTable 映射到內存中,提供更快的查找。

MemTable 達到一定大小會被 flush 到硬盤中變成 SSTable。在不同的 SSTable 中可能存在相同的 Key 記錄。但這樣會帶來一些問題:

  • 冗余存儲。對於某個 Key,除了最新的記錄,其他記錄都是冗余無用的。所以我們需要進行 Compact 操作(合並多個 SSTable),來清除冗余的記錄。
  • 讀取時需要從最新的 SSTable 出發進行查詢,最壞情況下葯查詢完所有的 SSTable。可以通過索引或布隆過濾器來優化查找速度。

3. LSM-Tree讀寫數據

3.1 LSM-Tree寫數據流程

LSM 樹中,我們按照下面的步驟處理寫數據請求。

  1. 當收到寫請求,先將數據記錄在 WAL Log 中,用作故障恢復。
  2. 將數據寫入內存的 MemTable 中。為了有序,我們往往用跳表或紅黑樹實現。
    • 如果是刪除,則做墓碑標記
    • 如果是更新,則新記錄一條數據。
  3. MemTable 達到一定大小后,在內存中凍結,成為不可變的 ImmuTable MemTable。同時也要生成新的 MemTable 來提供服務。
  4. 內存中不可變的 MemTable 被 dump 到硬盤上的 SSTable 中,這也稱為 Minor Compaction。注意 L0 層的 SSTable 是沒有合並的,所以 key 在多個 SSTable 中往往會重疊、冗余。
  5. 當每層 SSTable 超過一定大小,就會周期性的進行合並,這也稱為 Major Compaction。這個階段回清除掉榮譽的數據,防止浪費空間。由於 SSTable 都是有序的,可以使用歸並排序進行高效合並。

3.2 LSM-Tree讀數據流程

  1. 當收到讀請求,現在內存中查詢,查詢到就返回。
  2. 如果沒有查詢到,由內存到磁盤,在各級 SSTable 中依次下沉,直到得到結果。

4. LSM-Tree的Compact策略

Compact 是 LSM 樹中的關鍵操作,只有 Compact 的策略合理,才能及時有效地清除冗余的數據。

先介紹以下幾個概念:

  • 讀放大。讀取數據時實際讀取的數據量大於真正的數據量。例如在不同層次的 Table 中查找。
  • 寫放大。寫入數據時實際寫入的數據量大於真正的數據量。例如寫入時觸發 Compact,導致寫入大量數據。
  • 空間放大。數據占用的磁盤空間比真正的數據大小要大很多。即冗余存儲。

4.1 size-tiered策略

image-20220226135543243

size-tiered 策略保證每層中每個 SSTable 的大小相近,同時限制每一層 SSTable 的數量。

如上圖,每層限制有 N 個 SSTable,每層數量達到 N 后,觸發 Compact 操作來合並這些 SSTable,放入下一層成為更大的 SSTable

當層數越來越大,單個 SSTable 的大小也會越來越大。該策略會導致空間放大比較嚴重。對每一層的 SSTable 來說,每個 key 的記錄也可能存在多份。只有該層執行 Compact 操作才會消除這些冗余記錄。

4.2 leveled策略

image-20220226135604657

leveled 策略限制每一層總文件的大小。

leveld 同樣將每一層划分為大小相近的 SSTable。並保證在一層內全局有序。這意味着與一個 Key 在每一層至多只有一條記錄,不存在冗余記錄。

image-20220226133220671

下面展示 leveled 的 Compact 策略

Ⅰ. L1 總大小超過 L1 本身大小限制。

image-20220226135520901

Ⅱ. LSM 樹從 L1 中選擇至少一個文件,然后把他和 L2 有交集的部分進行合並。生成的文件放在 L2。

如下圖,L1 第二個 SSTable 的 Key 的范圍覆蓋了 L2 中前三個 SSTable,那么就需要將 L1 中第二個 SSTable 與 L2 中前三個 SSTable 執行 Compact。

image-20220226135432189

Ⅲ. 如果 L2 合並后的大小超過 L2 的限制大小。則重復之前的操作,選至少一個文件然后合並到下一層。

image-20220226135403205

多個不相干的合並可以並發進行

image-20220226135317088

leveled 策略相比 size-tiered 策略來說,每層內的 Key 是有序、不重復的。這樣就很好地控制了冗余 Key 的量。

5. 查詢優化

查詢過程中我們發現,在原始情況下,我們需要遍歷所有的 SSTable。我們考慮以下方式,嘗試優化查詢的效率。

  • 壓縮。SSTable可以進行壓縮,而且不是壓縮整個 SSTable。而是根據局部性原理將數據分組。每個分組分別壓縮。這樣讀取數據的時候我們就不需要解壓縮整個文件,而是解壓縮部分 Group 即可讀取。
  • 緩存。SSTable 除了進行 Compaction,其他情況下是不可變的。所以我們可以將一次掃描到的 Block 進行緩存,提高下一次檢索的效率。
  • 索引/布隆過濾器。正常情況下,一次讀操作需要讀取所有 SSTable,再將結果合並后返回。但是對某些 Key 而言,有些 SSTable 根本不包含對應數據。所以我們可以為每個 SSTable 添加布隆過濾器。來判斷當前 SSTable 有沒有我們需要的 Key。
  • 合並。合並本身肯定可以優化數據的組織情況,提高查詢效率。但是也要注意查詢是非常消耗 CPU 和磁盤 IO 的操作。一般我們選在業務量不大的凌晨等情況進行合並。

6. LSM-Tree和B-Tree的比較

  • LSM-Tree 的寫放大問題比 B-Tree 要好一些。因為 B 樹寫入的頁分裂操作實在太消耗磁盤 IO。
  • LSM-Tree 可以支持更好的壓縮。由於碎片,B-Tree 無法使用某些磁盤空間,而 LSM-Tree 會定期重寫來消除碎片。
  • LSM_Tree 在執行壓縮操作時,很容易發生讀寫請求等待的問題。而 B-Tree 的響應延遲則更具確定性。
  • B-Tree 中的每個鍵都位於索引中的每個位置,而日志結構的存儲引擎可能在不同的段中有相同鍵的多個副本。如果數據庫希望提供嚴格的事務語義,B-Tree 要更容易實現一些,因為鎖可以定義到樹中。


免責聲明!

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



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