LSM Tree(Log Structured Merge Trees)資料組織方式被應用於多種資料庫,如LevelDB、HBase、Cassandra等,下面我們從為什麼使用LSM tree、LSM tree的實現思路兩方面介紹這種儲存組織結構,完成對LSM tree的初步瞭解。
儲存背景回顧
LSM tree相較B+樹或其他索引儲存實現方式,提供了更好的寫效能。究其原因,我們先回顧磁碟相關的一點背景知識。
順序操作磁碟的效能,較隨機讀寫磁碟的效能高很多,我們實現資料庫時,也是圍繞磁碟的這點特性進行設計與優化。如果讓寫效能最優,最佳的實現方式就是日誌型(Log/Journal)資料庫,其以追加(Append)的方式寫磁碟檔案。
有得即有舍,萬事萬物存在權衡,帶來最優寫效能的同時,單純的日誌資料庫讀效能很差,為找到一條資料,不得不遍歷資料記錄,要實現範圍查詢(range)幾乎不可能。為優化日誌型資料庫的讀效能,實際應用中通常結合以下幾種優化措施:
二分查詢(Binary Search): 在一個資料檔案中使用二分查詢加速資料查詢
雜湊(Hash): 寫入時通過雜湊函式將資料放入不同的桶中,讀取時通過雜湊索引直接讀取
B+樹: 使用B+樹作為資料組織儲存形式,保持資料穩定有序
外部索引檔案: 除資料本身按日誌形式儲存外,另對其單獨建立索引加速讀取
以上措施都很大程度提升了讀效能(如二分查詢將時間複雜度提升至O(log(N))),但相應寫效能也有折損,第一寫資料時需要維護索引,這視索引的實現方式,最差情況下可能涉及隨機的IO操作;第二如果用B+樹等結構組織資料,寫入涉及兩次IO操作,先要將資料讀出來再寫入。
LSM Tree儲存結構
LSM tree儲存實現思路與以上四種措施不太相同,其將隨機寫轉化為順序寫,儘量保持日誌型資料庫的寫效能優勢,並提供相對較好的讀效能。具體實現方式如下:
1. 當有寫操作(或update操作)時,寫入位於記憶體的buffer,記憶體中通過某種資料結構(如skiplist)保持key有序
2. 一般的實現也會將資料追加寫到磁碟Log檔案,以備必要時恢復
3. 記憶體中的資料定時或按固定大小地刷到磁碟,更新操作只不斷地寫到記憶體,並不更新磁碟上已有檔案
4. 隨著越來越多寫操作,磁碟上積累的檔案也越來越多,這些檔案不可寫且有序
5. 定時對檔案進行合併操作(compaction),消除冗餘資料,減少檔案數量
以上過程用圖表示如下:
LSM Tree儲存結構的寫操作,只需更新記憶體,記憶體中的資料以塊資料形式刷到磁碟,是順序的IO操作,另外磁碟檔案定期的合併操作,也將帶來磁碟IO操作。
LSM tree儲存結構的讀操作,先從記憶體資料開始訪問,如果在記憶體中訪問不到,再順序從一個個磁碟檔案中查詢,由於檔案本身有序,並且定期的合併減少了磁碟檔案個數,因而查詢過程相對較快速。
合併操作是LSM tree實現中重要的一環,LevelDB、Cassandra中,使用基於層級的合併方式(Levelled compaction),生成第N層的時候,對N-1層的資料進行排序,使得每層內的資料檔案之間都是有序的,但最高層除外,因為該層不斷有資料檔案產生,因而只是資料檔案內部按key有序。
除最高層外,其他層檔案間資料有序,這也加速了讀過程,因為一個key對應的value只存在一個檔案中。假設總共有N層,每層最多K個資料檔案,最差的情況下,讀操作先遍歷K個檔案,再遍歷每層,共需要K+(N-1)次讀盤操作。
總結
LSM tree儲存框架實現的思路較簡單,其先在記憶體中儲存資料,再定時刷到磁碟,實現順序IO操作,通過定期合併檔案減少資料冗餘;檔案有序,保證讀取操作相對快速。
我們需要結合實際的業務場景選擇合適的儲存實現,不存在萬金油式的通用儲存框架。LSM tree適用於寫多、讀相對少(或較多讀取最新寫入的資料,該部分資料存在記憶體中,不需要磁碟IO操作)的業務場景。