本文是閱讀了DDIA的第三章後整理的讀書筆記,總結了什麼是LSM存儲引擎,以及在實現上的一些細節優化。數據庫
LSM一詞最先來自於Partrick O'Neil et al.發表的文章[1],全稱爲"Log-Structured Merged-Tree"。後來,Google發表的Bigtable論文[2]將其發揚光大。apache
目前全部基於該思想實現的存儲引擎,咱們均可以稱之爲「LSM存儲引擎」,例如:分佈式
不一樣於傳統的基於B+樹的數據庫存儲引擎,基於LSM的引擎尤爲適合於寫多讀少的場景。oop
咱們先從一個最簡單的存儲引擎示例出發,而後再描述LSM引擎的基本原理。性能
一個最基本的存儲引擎,須要支持下面兩個操做:優化
咱們知道,磁盤特別是機械硬盤,其隨機寫的速度是很是慘不忍睹的。可是,若是咱們是順序寫磁盤的話,那速度跟寫內存是至關的:由於減小了尋道時間和旋轉時間。並且順序寫的狀況下,還能將數據先放到buffer,等待數量達到磁盤的一頁時,再落盤,能進一步減小磁盤IO次數。ui
因此,這裏咱們規定,每一次寫數據都追加到數據文件的末尾。spa
#!/bin/sh
# usage: ./Put key value
echo "$1:$2" >> simpledb.data
複製代碼
注:若是對同一個key寫屢次,最終以最後一次的值爲準(即對於讀請求,應返回最後一次寫入的值)。線程
要從數據文件裏面查詢:設計
# usage: ./Get key
grep "^$1:" simpledb.data | sed -e 's/^://g' | tail -n 1
複製代碼
直接從數據文件查詢的效率是很低的:咱們須要遍歷整個數據文件。
這時候就須要「index」來加快讀操做了。咱們能夠在內存中保存一個key到文件偏移量的映射關係(哈希表索引):在查找時直接根據哈希表獲得偏移量,再去讀文件便可。
固然,加了索引表也相應地增長了寫操做的複雜度:寫數據時,在追加寫數據文件的同時,也要更新索引表。
這樣的建索引的方式有個缺點:由於索引map必須常駐內存,因此它無法處理數據量很大的狀況。當內存沒法加載完整索引數據時,就沒法工做了。
咱們再來看看另一個問題:傳統的B+樹,每一個key只會存一份值,佔用的磁盤空間是跟數據量嚴格對應的。可是在追加寫的方案中,磁盤空間是永無止盡的,只要這個系統在線上運行,產生寫請求,文件體積就會增長。
解決文件無限增加的方法就是 compaction:
以上就是一個簡單的基於內存索引+文件分段並按期壓縮的存儲引擎。能夠看到,它可以提供很好的寫入性能,可是沒法應對數據量過大的場景。
假定如今咱們要存儲N
對key-value
,那麼咱們一樣須要在索引裏面保存N
對key-offset
。可是,若是數據文件自己是按序存放的,咱們就不必對每一個key建索引了。咱們能夠將key劃分紅若干個block
,只索引每一個block
的start_key
。對於其它key,根據大小關係找到它存在的block
,而後在block
內部作順序搜索便可。
在LSM裏面,咱們把按序組織的數據文件稱爲SSTable
(Sorted String Table)。只保存block
起始key的offset的索引,咱們稱爲「稀疏索引」(Sparse Index)。
並且,有了block
的概念以後,咱們能夠以block
爲單位將數據進行壓縮,以達到減小磁盤IO吞吐量。
SSTable
呢?咱們能夠在內存裏面維護一個平衡二叉樹(例如AVL樹或者紅黑樹)。每當有Put(Key, Value)
請求時,先將數據寫入二叉樹,保證其順序性。當二叉樹達到既定規模時,咱們將其按序寫入到磁盤,轉換成SSTable
存儲下來。
在LSM裏面,咱們把內存裏的二叉樹稱爲memtable
。
注意,這裏的
memtable
雖然也是存在內存中的,可是它跟上面說的稀釋索引不同。對每個SSTable
,咱們都會爲它維護一個稀疏的內存索引;可是memtable
只是用來生成新的SSTable
。
對SSTable
,咱們一樣是經過 segment + compaction 來解決磁盤佔用的問題。
分segment
在memtable
轉SSTable
的時候就已經作了。
compaction
則依賴後臺線程按期執行了。可是對於有序的的SSTable
,咱們可使用歸併排序的思路來合併和壓縮文件:
若是在將memtable
轉存SSTable
時,進程掛掉了,怎麼保證未寫入SSTable
的數據不丟失呢?
參考數據庫的redo log
,咱們也能夠搞一個log
記錄當前memtable
的寫操做。在有Put
請求過來時,除了寫入memtable,還將操做追加到log。當memtable
成功轉成SSTable
以後,它對應的log
文件就能夠刪除了。在下次啓動時,若是發現有殘留的log
文件,先經過它恢復上次的memtable
。
對於查詢那些不存在的key,咱們須要搜索完memtable
和全部的SSTable
,才能肯定地說它不存在。
在數據量不大的狀況下,這不是個問題。可是當數據量達到必定的量級後,這會對系統性能形成很是嚴重的問題。
咱們能夠藉助Bloom Filter(布隆過濾器)來快速判斷一個key是否存在。
布隆過濾器的特色是,它可能會把一個不存在的key斷定爲存在;可是它毫不會把一個存在的key斷定爲不存在。這是能夠接受的,由於對於極少數誤判爲存在的key,只是多幾回搜索而已,只要不會將存在的key誤判爲不存在就行。並且它帶來的好處是顯而易見的:能夠節省大量的對不存在的key的搜索時間。
上文已經提到,咱們須要對SSTables
作合併:將多個SSTable
文件合併成一個SSTable
文件,並對同一個key,只保留最新的值。
那這裏討論的合併策略(Compaction Strategy)又是什麼呢?
A compaction strategy is what determines which of the sstables will be compacted, and when.
也就是說,合併策略是指:1)選擇何時作合併;2)哪些SSTable
會合併成一個SSTable
。
目前普遍應用的策略有兩種:size-tiered
策略和leveled
策略。
size-tiered
策略。leveled
策略。這裏簡要介紹下兩種策略的基本原理。後面研究LevelDB
源碼時再詳細描述leveled
策略。
size-tiered
策略簡稱STCS(Size-Tiered Compaction Strategy)。其基本原理是,每當某個尺寸的SSTable
數量達到既定個數時,合併成一個大的SSTable
,以下圖所示:
它的優勢是比較直觀,實現簡單,可是缺點是合併時的空間放大效應(Space Amplification)比較嚴重,具體請參考Scylla’s Compaction Strategies Series: Space Amplification in Size-Tiered Compaction。
空間放大效應,好比說數據自己只佔用2GB,可是在合併時須要有額外的8G空間才能完成合並,那空間放大就是4倍。
leveled
策略STCS
策略之因此有嚴重的空間放大問題,主要是由於它須要將全部SSTable文件合併成一個文件,只有在合併完成後才能刪除小的SSTable文件。那若是咱們能夠每次只處理小部分SSTable
文件,就能夠大大改善空間放大問題了。
leveled
策略,簡稱LCS(Leveled Compaction Strategy),核心思想就是將數據分紅互不重疊的一系列固定大小(例如 2 MB)的SSTable
文件,再將其分層(level)管理。對每一個Level
,咱們都有一份清單文件記錄着當前Level
內每一個SSTable
文件存儲的key的範圍。
Level和Level的區別在於它所保存的SSTable
文件的最大數量:Level-L
最多隻能保存 10 L 個SSTable
文件(可是Level 0
是個例外,後面再說)。
注:上圖中,"run of"就表示一個系列,這些文件互不重疊,共同組成該
level
的全部數據。Level 1
有10個文件;Level 2
有100個文件;依此類推。
下面對照着上圖再詳細描述下LCS
壓縮策略:
先來看一下當Level >= 1
時的合併策略。以Level 1
爲例,當Level 1
的SSTable
數量超過10個時,咱們將多餘的SSTable
轉存到Level-2
。爲了避免破壞Level-2
自己的互不重疊性,咱們須要將Level-2
內與這些待轉存的SSTable
有重疊的SSTable
挑出來,而後將這些SSTable
文件從新合併去重,造成新的一組SSTable
文件。若是這組新的SSTable
文件致使Level-2
的總文件數量超過100個,再將多餘的文件按照一樣的規則轉存到Level-3
。
再來看看Level 0
。Level 0
的SSTable
文件是直接從memtable
轉化來的:你無法保證這些SSTable
互不重疊。因此,咱們規定Level 0
數量不能超過4個:當達到4個時,咱們將這4個文件一塊兒處理:合併去重,造成一組互不重疊的SSTable
文件,再將其按照上一段描述的策略轉存到Level 1
。
[1] Patrick O’Neil, Edward Cheng, Dieter Gawlick, and Elizabeth O’Neil: 「The Log- Structured Merge-Tree (LSM-Tree),」 Acta Informatica, volume 33, number 4, pages 351–385, June 1996. doi:10.1007/s002360050048
[2] Fay Chang, Jeffrey Dean, Sanjay Ghemawat, et al.: 「Bigtable: A Distributed Storage System for Structured Data,」 at 7th USENIX Symposium on Operating System Design and Implementation (OSDI), November 2006.