深入理解LSM-Tree
深入理解LSM-Tree
- 基礎概念
- compaction策略
- Size-tired compaction strategy(STCS)/Tiered
- leveled compaction
- Leveled-N
- Hybrid
- Time-Window
- 比較
- 工業實現
- leveldb
- RocksDB
- Write Stalls
- scyllaDB/cassandra
- hbase
- TiKV/Titan
- 學術研究
- Dostoevsky
- 參考鏈接
lsm-tree的背景、定義和適用場景本文不再詳述。本文意在論述LSM的存儲引擎、工業取舍以及發展現狀。
基礎概念
-
The on-disk data is organized into sorted runs of data. Each run contains data sorted by the index key. A run can be represented on disk as a single file, or alternatively as a collection of files with non-overlapping key ranges. To perform a query on a particular key to get its associated value, one must search in the Level 0 tree and each run.
- 可見,每一個run都是有序的數據塊。
compaction策略
LSM-Tree存儲引擎的工作流程如下:
從上面的存儲流程可以看出,LSM-Tree的性能主要取決于Compaction策略,它主要分為兩類,tiered compaction和leveled compaction
- tiered流派從Bigtable->HBase->Cassandra一脈相承,后面又有MyRocks、InfluxDB、Kudu等等
- Leveled流派工程實現主要有LevelDB->RocksDB
Size-tired compaction strategy(STCS)/Tiered
tiered適合write-intensive workload,有較低的寫放大,缺點是讀放大和空間放大較高。
tiered compaction的策略是:memtable達到閾值之后刷入下一層(落盤)作為一個run,每層的run達到一定數量之后,將當前層所以的run進行compaction并刷入下一層,循環往復。這種策略給人直觀的感受是,越下層的run越大,當然實際的策越要比描述的復雜,因為run與run之間重疊的部分是不確定的,有可能完全重疊,也有可能完全不重疊。
- 由于所有的上層level的數據可以直接追加寫入下層,所以寫放大比較低
- 由于每層都有多個run,且每個run中的數據可以重疊,所以點讀操作需要由上到下遍歷每一個run,讀放大較嚴重。更不要提range查詢肯定要合并每一層的每一個run的查詢結果,讀放大更嚴重。
- 由于每層有多個run,每個run都可能含有重疊數據,所以空間放大較為嚴重。空間放大可以說是STCS最嚴重的問題了。Scylla的文章通過實驗論證了這個問題
- 每層的多個run需要先合并,才能寫入下層,因此有可能導致臨時空間較大。耗時也較長
- 這里討論一個特例time-series data。key是時間,幾乎是恒定遞增的,因此越是下層,run越是不重疊,寫入和查詢的策略都可以做對應的優化。
leveled compaction
leveled compaction和上面的STCS相比,降低了空間放大和讀放大,比較適合read-intensive workload,當然這個也只是相對而言,如果真的是讀很多,不如用B+ Tree。
leveled compaction每層只維護一個run,按照key排序可以分為多個partition(SSTable file)。每層容量達到一定限制后,選擇某個sstable與上層merge。
- 由于每層只有一個run,所以降低了讀放大和空間放大。但是點查詢仍然有可能會遍歷所有的run、范圍查詢必然會遍歷所以的run。
- 由于leveled compaction上層與下層merge的過程有可能涉及到上下層所有的數據,可能造成上層run全量重寫,導致寫入放大,但是由于每個run可以分為多個partition(sstable),因此可以節約部分臨時空間。
Leveled-N
不同于leveled,Leveled-N允許每層多個Sorted run。Dostoevsky paper引入了一種Fluid LSM:允許最高層只有一個run,而其它層可以有多個run。
Hybrid
這里討論scylladb實現的Hybrid模式。主要體現在這篇文章,其希望推出一種模式,可以吸納tiered和leveled兩者的優點。scylladb引入Hybrid主要為了優化tiered糟糕的空間放大。我推測主要有以下幾點優化:
Time-Window
這里主要講scylladb/Cassandra針對時序數據引入的Time-Window compaction,時序數據有以下特點:
這種數據狀況下,通過將不同的時間分配在不同的Time-Window中,將SSTable分配成了一個個time bucket,實現形式上采用tiered compaction,不同的time bucket不會合并(優化讀操作)。由于overlap并不多,所以compaction壓力不大,空間放大也不大。由于數據分布較好,查詢也較方便。
比較
scylladb的文章總結了以下表格:
| Write-only | 2x peak space | 2x writes | Best | - |
| Overwrite(指同一個key多次update) | Huge peak space | write amplification | high peak space bug not like size-tiered | - |
| Read-mostly, few updates | read amplification | Best | read amplification | - |
| Read-mostly, but a lot of updates | read and space amplification | write amplification may overwhelm | read amplification | - |
| Time series | write,read,and space amplification | write and space amplification | write and read amplification | best |
工業實現
leveldb
顧名思義,levelDB使用leveled compaction,其CRUD流程如下:
levelDB代碼清晰,代碼量少,閱讀方便,不過缺少大規模工程應用場景。
RocksDB
LSM-Tre工業級實現,和levelDB相比,參數更多,優化更多,使用更靈活,Features Not in LevelDB這篇文章介紹了RocksDB相對于levelDB所做的優化,我選擇一些優點列舉如下:
Write Stalls
RocksDB會在某種情況下限制前臺寫請求的速度,稱為Write Stalls。產生的原因不外乎因為某種原因,compaction的速率趕不上前臺write的速率了。這種現象如果持續下去,會產生以下結果:
這種情況下需要將前臺寫請求降速,直到compaction執行到某種可以繼續高效處理前臺讀寫請求的程度。
一般情況下,Write Stall產生的原因有以下幾種可能:
scyllaDB/cassandra
scyllaDB/cassandra默認采用tiered,可以配置為levled、Hybrid或Time-Window。只有Hybrid是ScyllaDB獨有。實現細節如上所述
hbase
HBase是一個分布式,版本化,面向列的開源數據庫(面向列族Column Family,在)、分布式持久化KV數據庫。HDFS(僅支持追加寫)為Hbase提供可靠的底層數據存儲服務,MapReduce為Hbase提供高性能的計算能力,Zookeeper為Hbase提供穩定服務和Failover機制,因此我們說Hbase是一個通過大量廉價的機器解決海量數據的高速存儲和讀取的分布式數據庫解決方案。Hbase適合存儲PB級別的海量數據,雖然當個請求是時延不低,但是可以通過并行請求增大吞吐,并且單個IO的時延下降并不多。
架構和bigtable比較類似,通過key-range,將數據水平切分到多臺服務器上,數據持久化在HDFS中。這點和現代的應用如tidb略有不同,其是將數據切分之后通過一致性協議存儲在多個RSM上,每個RSM的數據引擎為LSM-Tree
Hbase 技術細節筆記(上)
hbase在實現中,是把整個內存在一定閾值后,flush到disk中,形成一個file,這個file的存儲也就是一個小的B+樹,因為hbase一般是部署在hdfs上,hdfs不支持對文件的update操作,所以hbase這么整體內存flush,而不是和磁盤中的小樹merge update。內存flush到磁盤上的小樹,定期也會合并成一個大樹。整體上hbase就是用了lsm tree的思路。
因為小樹先寫到內存中,為了防止內存數據丟失,寫內存的同時需要暫時持久化到磁盤,對應了HBase的HLog(WAL)和MemStore
MemStore上的樹達到一定大小之后,需要flush到HRegion磁盤中(一般是Hadoop DataNode),這樣MemStore就變成了DataNode上的磁盤文件StoreFile,定期HRegionServer對DataNode的數據做merge操作,徹底刪除無效空間,多棵小樹在這個時機合并成大樹,來增強讀性能。
TiKV/Titan
TiKV使用RocksDB作為存儲引擎,并且實現了Titan作為RocksDB 的高性能單機 key-value 存儲引擎插件。以支持大value(Blob),其核心特性在于支持將 value 從 LSM-tree 中分離出來單獨存儲,以降低寫放大。
其主要設計靈感來源于 USENIX FAST 2016 上發表的一篇論文 WiscKey。WiscKey 提出了一種高度基于 SSD 優化的設計,利用 SSD 高效的隨機讀寫性能,通過將 value 分離出 LSM-tree 的方法來達到降低寫放大的目的。
Titan 適合在以下場景中使用:
學術研究
Dostoevsky
這篇論文詳細地分析了各種操作在tired和leveled不同策略下的i/o復雜度,使用了level num、相鄰level size比、entry數量、buffer size等一系列相關變量推導出不同操作具體的I/O代價公式和空間放大,對于point read還考慮了bloom filter帶來的影響,range query分了short和long兩種來分析。并且提出了優化策略lazying leveling,是tiering和leveling的結合體,最大一層使用leveling其它層使用tiering,lazying適合包含update、point lookup和long range lookup的混合負載,tiering適合大多為update的負載,leveling適合大多為lookup的負載。通過控制merge頻率在tiering、leveling和lazying leveling幾種不同策略下轉換以適應不同的workload,稱作fluid LSM((類似rocksdb的leveled-n)),并提出Dostoevsky,在執行期間動態計算最優配置以到達最大吞吐。
從作者的分析中可以看出compaction這個機制的復雜程度,要使用一套通用機制在不同場景下消耗最少的資源帶來最好的性能基本是不可能的,實際工業界實現中需要考慮更多的因素(cache、delete、任務拆分粒度、復用技術等),因此大部分系統使用多種策略多個參數用以適配不同場景。
參考鏈接
總結
以上是生活随笔為你收集整理的深入理解LSM-Tree的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布式块存储QoS限速算法介绍与实践以及
- 下一篇: LevelDB源码解读