【SOSP 2017】PebblesDB 技術解讀
閱讀 sosp 2017 《PebblesDB : building key-value stores using fragmented log-structured merge trees》 的筆記。本篇只記錄了論文核心思路,很多細節讀者還是需要閱讀原文。
1. Background
1.1 Basic Introduction
這篇還是研究的 LSM-tree 的優化,主要還是針對 LSM-tree 的老問題,write amplification 做優化。關於 LSM-tree 的基本原理和存在的問題,可以參見之前一篇博文 【FAST 2016】Wisckey-Separating Keys from Values 的第一章節。這裡給出論文中一組關於 LSM-tree 的 write amplification 的數據:
由上圖不難發現傳統的 LSM-tree 存在嚴重的 write amplification。為了解決這個問題,論文作者受 Skiplist 的啟發,設計了一種 Fragmented Log-Structured Merge Trees data structure (FLSM-tree),並開發出了一個 key-value 單機存儲原型 PebblesDB,可以有效的減少 write amplification;由 Figure.1 可以看出, PebblesDB 的 write amplification 遠小於 leveldb 和 rocksdb。
1.2. Write Amplifcation Root Cause
再介紹 FLSM-tree 之前,先來分析下 LSM-tree write amplification 的根本原因,也是論文優化的動機和思路,一句話描述就是:一些 key-value 在某一層中反覆的 compaction ,從而造成被rewrite。下面將通過一個例子著手來解釋,如下圖:
我們知道 level n 往 level n+1 挪動的時候,是通過 Compaction,其大致的過程是,這裡假定 level n 中的 sstable A 要通過 Compaction merge 到 level n+1,那麼就會在 level n+1 找到所有和 level A [min key, max, key] 有交集的 sstable 做歸併排序合併成 level n+1 新的 sstable,(1)例如 Figure 2. 的 t2 時刻, level 0 的 sstable [20, 210] 與 level 1 的兩個 sstable 合併,這個 1,10 ,100 和 200,210,400 被重寫了第一遍;(2)然後在 t4 時刻,發生第二次 Compaction,由於 level 0 的 sstable 和 level 1 的兩個 sstable 又都有交集,這個時候 1,10 ,100 和 200,210,400 又被重寫了第二遍;(3)在 t6 時刻,同理由於由於 level 0 的 sstable 和 level 1 的兩個 sstable 又都有交集,這個時候 1,10 ,100 和 200,210,400 又被重寫了一遍,這個時候已經被重寫了第三遍了。
由上面的例子可見一斑,Compaction 造成了一些 key-value 反覆的 rewrite,從而造成了 LSM-tree 的 write amplification,如果有什麼辦法可以控制一個 key-value 在某一層被 rewrite 的次數就好了,那麼結合 LSM-tree 的層數限制,每個 key-value 最壞情況的 write amplification 都將是有上界的。這也正是後面要介紹 FLSM-tree 能做到的。最終 PebblesDB 通過減少 write amplification 來提高了 throughput,
2. PebblesDB
PebblesDB 受 Skiplist 的啟發,設計了 FLSM-tree,其可以保證 so ensures that data is written exactly once in most levels,是不是很牛。
2.1. Skiplist
既然受 Skiplist 的啟發,那麼我們先來看看 Skiplist ,在剛開始接觸 Skiplist 的時候,並沒有很好的理解 Skiplist 的精妙之處,實際上 Skiplist 有一個很精妙的東西叫 Guard,如下圖,給出了一個邏輯的 Skiplist,
其中最低層的 level 1 存放了所有的數據,並且保持有序,上面的 level 2 和 level 3 上的所有節點其實都是 Guard,那麼什麼是 Guard 呢?其實就是高速通道,也就是索引層,實際上和 B+ tree 很像,其基本結構是一樣的,只是節點插入和刪除的時候,索引的層的選擇有一些差異,Skiplist 是基於一定的隨機選擇策略來選擇索引 Guard,而 B+ 則是一棵有序的搜索樹,通過節點的合併和分裂來實現的,但是本質上是一樣的。
2.2. FLSM-tree
FLSM-tree 借鑒 Skiplist 的 Guard 思想,給 LSM-tree 的每個 level 加了一些 Guard 數據,通過 Guard 來組織數據,儘管很簡單,但是思路很巧妙,下面通過一個例子來說明:
level n 有數據 {1, 20, 45,101, 245}
level n+1有三個guard分別為:1、40、200
那麼 level n 往level n+1 Compaction的時候不會去和其他的 sstable 合併,而是將自己的 sstable 按照 guard 切分成多個 sstable(也就是fragement),合併到 level n+1之後就變成了:
level n+1:
guard: | 1 | 40 | 200 |
| { 1,20 } | { 45,101 } | {245} |
這樣就保證了每次的只有這個 sstable 在往高的那個 level compaction 的時候只會產生一次切分的 rewrite。而且 sstable 按照 guard 來管理,而且不同的 guard 的之間是沒有影響的,所以,可以並發的compaction。
3. Summary
PebblesDB 借鑒 Skiplist 的思路非常的精妙,雖然簡單,但是結合 1.2. 的例子可以看到其明顯的減少了 Write amplification,而一旦減少 write amplification 的,那麼將給 write 的性能有相當的提升。當然還有更多這裡沒有介紹,感興趣的可以詳細的閱讀論文,這裡不再詳細的描述:
- guard 的選擇,類似 skiplist ,guard 的選擇是很關鍵的,它是每個 level 的 fragement 是否均衡的關鍵,而 fragement 的均衡性將大大的影響到 read 的性能
- 數據是動態的在不同的 level 之間遷移的,那麼 guard 肯定也不是一層不變的,如何 insert/delete 也是一個非常重要問題,而且一旦 guard 發生變化,那麼相應的 fragement 的數據將產生大量的移動和 rewrite,是一筆不小的開銷,處理起來也是相當的不容易
- read 的性能問題,由於 Compaction 的時候,sstable 只是簡單地被切分到不同的 fragement ,那麼意味不同的 fragement 的數據被分散在不同的 sstable 文件中,而且這些 sstable 文件相互之間是可能有交集,那麼自然對讀的性能是由影響 ?這其實也是第1、2 個問題需要考慮的
其中在參考文獻 [2] 有個描述覺得非常贊同:系統對序的要求越強烈,寫放大越嚴重
References
[1]. PebblesDB building key-value stores using fragmented log-structured merge tree. http://www.cs.utexas.edu/~vijay/papers/sosp17-pebblesdb.pdf
[2]. PebblesDB讀後感. https://zhuanlan.zhihu.com/p/32225460
推薦閱讀: