LSM-Tree 與 B-Tree


外部存儲

數據庫管理系統DBMS是現代應用中不可或缺的一部分,其中一個重要原因是其隱藏了外存管理的細節,並為應用層提供了高效、易用的數據檢索Retrieval與持久化Persistence功能。

外存具有容量大、成本低、斷電非易失等優點,但同時也存在尋址慢、訪問粒度粗的問題:

  • 內存尋址速度快(ns 級),尋址單位小(byte)
  • 外存尋址速度慢(ms 級),尋址單位大(≥4kb)

數據庫的讀寫性能取決於外存訪問效率,而優化外存訪問的手段有:

  • 減少外存訪問次數:借助寫緩沖Buffer、讀緩存Cache的方式,將熱點數據臨時存儲在內存,避免頻繁的外存訪問
  • 避免隨機尋址:使用預寫日志WAL對寫操作進行優化,將隨機寫操作簡化為順序的追加操作
  • 單次讀取盡可能多的數據:使用高密度的外存索引Index來組織數據,通過有序性提高檢索效率

預寫日志

預寫日志系統 WALWrite-Ahead Logging是一種用於提高數據庫寫性能的常見手段,被廣泛應用於持久化數據庫中。

數據庫中的狀態可以分為兩部分:

  • WAL 日志:所有對數據庫的變更都先寫入這個日志,並在事務提交時進行持久化,防止已提交數據丟失,已提交的日志數據會被定期清理
  • DB 文件:包含所有已經交的數據、索引信息,數據長期存在不會消失

WAL 的核心思想是 日志先行

  • 寫數據時,變更操作首先追加到 WAL 日志末尾,WAL 會將數據順序刷到磁盤(提交成功)。異步線程會消費 WAL 中的變更消息(類似於隊列),將應用變更到 DB 文件中並重置 WAL
  • 讀數據時,需要同時讀取 WALDB 中的數據,並將兩者合並生成最新的記錄

由於追加 WAL 是順序的,可以將隨機的磁盤IO轉換為順序的磁盤IO,減少磁盤巡道時間,從而能夠更有效地提升了磁盤的吞吐量。

數據庫重啟過程中會檢查 WAL 日志,任何尚未附加到 DB 數據頁的記錄都將從日志記錄中重放,每次提交事務時不再需要(為了保證數據安全)把數據頁沖刷到磁盤,有效地提升了事務吞吐量。

WAL 只允許在尾部追加數據 Append-Only ,不允許修改日志記錄。這種不變性 Immutability 有利於並發控制:刪改數據只能通過追加新的日志實現,因此修改前無需對數據加鎖,直接在日志末尾追加新的記錄即可。

然而 WAL 的體積也不可能無限增長,系統需要周期性周期性的清理無用的日志記錄,減少文件碎片,釋放磁盤空間。

索引

索引是一種附加的數據結構,以犧牲空間和寫入速度為代價,換取更快的檢索速度。最常用的索引結構莫過於 HashTree

Hash

  • 維護方便,單個 key 的隨機查找速度極快,一般都是常量級的 O(1)
  • 無法支持范圍查找,隨着記錄的增長,哈希沖突率上升,導致查找速度下降
  • 整個索引需要保證能夠放入內存,否則就無法發揮其速度優勢

Tree

  • 支持范圍查找,查找速度穩定,二叉平衡樹可以保證 O(log2n)
  • 維護成本較高,插入數據時需要重新平衡樹,每個節點的需要額外的指針存儲空間
  • 大數據量的情況下查找性能比較穩定,具有多種變種算法可以適配各種應用場景

由於數據庫需要管理海量的數據,因此 Tree 便成為外存索引的不二之選。
下面介紹其中最具代表性兩類索引結構:B-TreeLSM-Tree

B-Tree

最基礎的 Tree 莫過於二叉查找樹。其查找數據的方式,就是從根節點開始逐層向下遍歷,直到找到目標節點。但是當數據量比較大的時候,會有以下問題:

  • 節點之間的地址不連續,每次在節點之間的跳轉訪問時,都要進行尋址,訪問效率不高
  • 最壞情況下的訪問效率取決於樹的高度,當數據量大時,即便是平衡樹,其高度也很可觀

B-Tree 是一種用於處理海量數據的平衡多路查找樹,其主要改進是對二叉樹中間節點進行了合並,通過平衡算法和分叉因子 b,可以將樹高度控制在logbn 的級別,對外存訪問更為友好:

  • 每個節點包含盡可能多的數據,可以一次讀出大量的數據,減少對外存的訪問次數
  • 有效地降低了整棵樹的高度,在大數據量的情況下能夠保證較少的訪問次數

這意味着:只需要很少的磁盤 IO,就能夠對大量的數據進行高效的查找操作。

B-Tree 在作為外存索引使用時:

  • 根節點會常駐內存,其余節點存儲在磁盤上,從而能夠減少一次磁盤 IO
  • 按照頁來組織數據,每個節點大小需對應一個完整的頁(磁盤IO的基本單位是物理塊block,操作系統使用邏輯頁page管理應用程序的地址映射)
  • 為了保證數據的安全性,在對索引數據進行修改前要先寫 WAL,因此每次寫操作會造成至少兩次磁盤寫(忽略寫緩存)
  • 寫入的 Key 如果是隨機或不連續的,可能會造成索引節點的多次分裂,影響寫入的效率(寫放大效應)
  • 在多次修改、刪除操作之后,索引文件中會產生比較多的空洞,造成磁盤空間的浪費,並且會影響讀性能(需要定期重建索引)

B+Tree 是對 B-Tree 的進一步改進:將 Key 與 Value 進行分離,非葉節點只保存 Key,所有 Value 下沉到葉子節點。
每個中間節點可以容納更多的 Key,進一步提高了中間節點的密度,在相同的數據量下,樹的高度要比 B-Tree 更低。

LSM-Tree

LSM-Tree 的全稱是 Log-Structured Merge Tree,相較於一種索引結構,其本質更接近於一整套完整的索引維護機制:

LSM-Tree 大致可以分為兩部分:

  • Memtable: 常駐內存的 KV 查找樹(可用 SkipList 替代) + 無序的 WAL 文件
  • SSTable (Sorted String Table): 一組存儲在磁盤的不可變文件(稀疏索引部分可選),存儲有序的鍵值對

寫入流程

1. 同步寫 Memtable

先將數據寫入 WAL 文件,然后修改內存中的 AVL,因此最優情況下,每次寫操作只有一次磁盤 I/O。

刪除操作並不會直接刪除磁盤中的內容,而是將刪除標記(tombstone)寫入 Memtable。當 Memtable 增大到一定程度后,則會轉換為 Immutable Memtable 並產生一個新的 Memtable 接受寫操作。

2. 異步寫 SSTable

后台會啟動一個合並線程,當 Immutable Memtable 達到一定數量,合並線程會將其寫入磁盤(Flush),生成 Level 0 的 SSTable 文件。

Level N 的 SSTable 文件數量到達閾值之后,會進行合並壓縮(Compaction)操作,在 Level N+1 生成新的 SSTable 文件。

SSTable 分為多層,單個文件的大小通常是上一層的 10 倍,每層可以同時包含多個 sst 文件,每個文件由多個 block 組成,其大小約為 32K,是磁盤 IO 的基本單位。

Level i (i > 0) 層的 SSTable 滿足:

  • 第 i 層所有文件均由 i - 1 層的 SSTable 合並排序而來,可以通過設定閾值(文件個數...)來控制合並的行為
  • 文件之間是有序的,且每個文件的 key 集合不會與其他文件有交集(Level 0 的 SSTable 除外)

讀取流程

首先中 Memtable 中進行查找,如果找不到,則按 Level 0、Level 1、... 的方式逐層向下遍歷.

一個 Key 可能同時存在於多層 SSTable 中,這種情況下以層數最小的記錄為准
為了提高熱點數據的讀取效率,提供了 sstable block cache 的功能,用於緩存讀取數據。

某些不存在的 Key 可能會導致較深的無用查找,通過使用 BloomFIlter 對 Key 進行過濾可以規避這一問題。

放大效應

  • 寫放大效應:一次寫操作,實際所需的磁盤 IO 次數不止一次
  • 讀放大效應:一次讀操作,實際所需的磁盤 IO 次數不止一次

對於讀寫負載較高的數據庫,性能瓶頸很有可能是磁盤的讀寫頻率。在這種情況下,讀寫放大會顯著影響性能:

在磁盤帶寬一定的情況下,放大效應越明顯,每次對數據庫的讀寫操作造成磁盤IO越多,每秒鍾能處理的數據庫操作次數越小

寫放大

Write B-Tree LSM-Tree
最優
  1. 將變更附加到 WAL 末尾(寫)
  2. 讀出 Key 所在的 page 到內存(讀)
  3. 在內存中對 page 進行修改
  4. 將修改后的 page 寫回磁盤(異步寫)
  1. 將變更附加到 WAL 末尾(寫)
  2. 修改內存中的 Memtable
最壞
  1. 將變更附加到 WAL 末尾(寫)
  2. 讀出 Key 所在的及其相關的 page 到內存(異步讀 x N)
  3. 在內存中對 page 進行合並、分裂、修改
  4. 將修改后的所有 page 寫回磁盤 (異步寫 x M)
  1. 將變更附加到 WAL 末尾(寫)
  2. 修改內存中的 Memtable
  3. 將 Memtable 序列化到磁盤中(異步寫 x 1)
  4. 對 SSTable 進行合並操作(異步寫 x K)
B-tree 平均需要寫兩次磁盤,一次是 WAL,另一次是寫樹節點對應的 page,並且是以整頁的方式進行存取的。

LST-Tree 平均只需要寫一次磁盤,即寫 WAL, 在少數情況下,一次寫入也有可能造成多次寫磁盤操作。

讀放大

Read B-Tree LSM-Tree
最優
  1. 命中緩存,Key 對應的 page 在內存中
  2. 返回 page 中對應的記錄
  1. 命中緩存,Key 對應的記錄在 Memtable 或者在 SSTable 的 block cache 中
  2. 返回 Memtable 或 cache 中對應的記錄
最壞
  1. 未命中緩存,根據 Key 加載出對應的 page(讀)
  2. 返回 page 中對應的記錄
  1. 未命中緩存,根據 Key 在 SSTable 中逐層向下查找對應的 block,並加載到 cache 中 (讀xM)
  2. 返回 cache 中對應的記錄
B-tree 最壞情況下需讀取的次數等於樹高,通常情況下為 2 - 3 次。

LST-Tree 由於引入了 SSTable 格式,最壞情況下讀取次數不可控。

對比

LSM-Tree 有着更小的寫放大效應,B-Tree 有着更小的讀放大效應。

LSM-Tree 能夠承載更高的寫入吞吐量,B-Tree 在隨機讀的情況下能夠提供更穩定的性能保障。

LSM-Tree 本身就是一種對讀寫的 trade-off,用更大的讀放大效應換取更小的寫放大效應。

更進一步的,LSM-Tree 可以通過調整合並策略Merge Policy在讀寫放大之間進行權衡。

總結

優點 缺點
B-Tree
  • 普適性強,適合更廣泛的應用場景,廣泛應用於各種 RMDB 中
  • 便於實現強事務,每個 Key 會嚴格對應一個 Value,不會出現相同 Key 同時存在多個的 Value 的現象
  • 需要配合 WAL 使用,需要額外的磁盤寫操作
  • 隨着刪改操作的增多,文件會包含越來越多的空間碎片,會降低磁盤利用率
LSM-Tree
  • 寫操作優化,將隨機寫轉換為順序寫,適合順序寫負載重的應用場景,特別是時序日志型應用
  • 文件碎片少,對 SSTable 進行合並時,同時也完成了壓縮操作,生成的文件更為緊湊
  • 應用場景有限,不適合隨機讀寫或非熱點數據的讀取
  • 需要使用獨立的后台線程來維護 SSTable,對 SSTable 的維護操作會與用戶操作爭搶磁盤帶寬


免責聲明!

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



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