LSM 優化系列(三)-- SILK- Preventing Latency Spikes in Log-Structured Merge Key-Value Stores ATC‘19
1. Latency Spike in LSM
在 USENIX Annual Technical Conference2019的定會上,該篇論文提出瞭解決LSM 架構在update 或者有部分update參與的場景下出現的長尾延時問題。
關於LSM架構下的長尾延時問題,之前在介紹rocksdb的Rate Limiter 實現原理 時提到過,這裡簡單描述一下。
1.1 LSM三種internal操作
- Flush 由write-buffer 寫入 L0
- L0-L1 Compaction 因為L0 層檔案之間允許有key的重疊(LSM 為了追求寫效能,使用append only方式寫入key,write-buffer一般是skiplist的結構),所以只允許單執行緒將L0的檔案通過compaction寫入L1
- Higher Level Compactions 大於L0層 的檔案嚴格有序,所以可以通過多執行緒進行compaction.
Flush 操作如下:
- 請求寫入到(write-buffer)memtable之中,當達到write_buffer_size大小 進行memtable switch
- 舊的memtable變成只讀的用來 Flush,同時生成一個新的memtable用來接收客戶端請求
- Flush的過程就是在L0 生成一個sst檔案描述符,將immutable 中的資料通過系統呼叫寫入該檔案描述符代表的檔案中。
L0–> L1 Compaction 操作如下:
- 將一個L0的sst檔案和多個L1 的檔案進行合併
- 目的是節省足夠的空間來讓write-buffer持續向L0 Flush
Higher Level Compactions操作如下:
對整個LSM進行GC,主要丟棄一些多key的副本 和 刪除對應的key的values
這個過程並不如L0–>L1的compaction 緊急,但是會產生巨量的IO操作,這個過程可以後臺併發進行。
1.2 長尾延時的原因
- L0滿, 無法接收 write-buff不能及時Flush,阻塞客戶端
因果鏈如下:
沒有協調好internal ops – 》 Higher Level Compactions 佔用了過多的IO --》L0–>L1 compaction 過慢 --》L0沒有足夠的空間 --》Write-buffer無法繼續重新整理。 - Flush 太慢,客戶端阻塞
因果鏈如下:
沒有協調好internal ops --》 Higher Level Comapction佔用了過多的I/O --》 Flushing 過程中沒有足夠的IO資源 --》Flushing 過慢 --》Write-buffer提早寫滿而無法切換成immutable memtable,阻塞客戶端請求。
綜上我們知道了在LSM 下客戶端長尾延時主要是由於三種 內部操作的IO資源未合理得協調好導致 最終的客戶端操作發生了阻塞。
針對長尾延時的優化我們需要通過協調內部的internal 操作之間的關聯,保證Flushing 優先順序最高,能夠佔用最多的IO資源;同時也需要在合理的時機完成L0–L1的Compaction 以及 優先順序最低但是又十分必要的Higher Level Compaction。
以下簡單介紹一下Rocksdb 內部原生的Rate Limiter對這個過程的優化。
綜上可以看到傳統LSM的長尾延時問題 主要是由於LSM 三個internal ops(Flush, L0–>L1 compaction ,Higher Level Compactions)的排程策略導致的。
2. 長尾延時的業界解決方案
2.1 Rocksdb
首先對比了Rocksdb 實現的LSM,開啟和關閉Compaction時的長尾延時情況如下:
可以看到開啟Internal ops(橘黃色部分) 之後 整個長尾延時相比於未開 高了接近4個量級。
2.2 Rate-Limited Rocksdb
通過限制 internal ops的I/O頻寬 是一個業界比較認可的限制長尾延時問題的方法,SILK開發人員也對Rocksdb自實現的Rate Limiter做了測試。關於Rate Limter的實現可以參考Rocklsdb Rate Limiter實現原理。
通過設定不同的Limit Rate來對長尾延時的情況進行統計,得到如下測試結果:
可以看出隨著 限制internal ops頻寬越來越高,那麼長尾延時 維持在較低的時間 越來越長。
但是這並不能無限制得增加internal ops的限制頻寬,因為隨著compaction 累積的量越多,後期會 較高概率有一次累積 更多的compaction 與上層吞吐來爭搶IO頻寬。
2.3 TRIAD
ATC’17 TRIAD TRIAD: Creating Synergies Between Memory,
Disk and Log in Log Structured Key-Value Stores 是一個非常有代表性的先進系統,來降低internal ops對長尾延時的影響,SILK對該系統也做了對應的測試。
TRIAD 通過排程 減少 L0–》L1 的compaction 降低 compaction 對client 的吞吐影響,但是這個問題會導致Higher Level Compactions的增加。所以上圖中可以看出在1000s以後較長的一段時間內,整體的長尾延時還是會增加。
2.4 Pebblesdb
關於Pebblesdb 的整體介紹可以參考Pebblesdb: Building Key-Value Stores Using FLSM
SILK也對pebblesdb的長尾延時做了整體的測試,在讀敏感的workload下(95:5)workload下,長尾延時保持在一個非常好的效果,但是在執行了8個小時之後,compaction大量接入,整體的系統效率被嚴重阻塞。
因為Pebblesdb執行guards內部存在重疊key,所以到後期大量的Higher level compactions搶佔了系統的資源,導致整個client的IO被阻塞,整個stall一直持續直到compaction完成終止。
2.5 Lessons Learned
- 對於較高的長尾延時出現的主要原因是 寫被存在於記憶體的write buffer 填滿阻塞了。
Write buffer滿主要有如下兩個原因
a. L0滿,造成write-buffer向L0 flush被終止。從而導致客戶端無法持續向新的write-buffer寫入。這裡L0滿的原因是,L0–》L1 Compaction的過程中沒有IO被更高層compaction 產生的I/O搶佔,導致L0 提前滿。
b. Flush slow,由於更高層的併發compaction導致Flush的IO被搶佔,從而產生Flush的速度沒有write-buffer的寫入速度快。 - 僅僅限制 internal ops的IO頻寬並無法解決Flush或者L0 ->L1 compaction等優先順序較高的任務被 Higher Level Compactions搶佔的問題。所以Rocksdb的 Rate Limiter 以及 Rocksdb的Auto tune等都會導致後續Higher Level compactions搶佔優先順序較高的任務的IO,從而間接導致write-buffer full.
- 近些年的一些LSM優化的方法 提出為了提升吞吐,將Compaction下沉到更高層來做,這在短期內能夠收穫較為穩定的延時以及較高的吞吐,但是在跑了很長一段時間之後就會出現大量的Compaction搶佔系統資源的問題,從而導致Client qps stall。
綜上 ,我們能夠得出如下結論,並不是所有的internal ops都需要平等共享系統資源,比如 Flush 以及 L0->L1 Compaction,就是優先順序比較高的internal ops,這一些操作不能及時完成,就會導致Client ops被stall。
3. SILK 實現
SILK 在總結了之前業界相關的優化方案,為了避免再次出現上文2.5中指出的Lesson learned,核心的設計思想將遵循如下三個原則。
3.1 SILK 設計原則
- 讓Internal ops都有能夠獲得IO頻寬的機會。SILK 在client 的流量洪峰時會減少Higher level Compaction的IO頻寬佔用,在client 流量低峰時增加Higher Level的Compactions的頻寬。
- 對LSM tree的internal ops進行優先順序排程。正如SILK將LSM的internal ops分為三種不同的操作,並排程優先順序來降低對Clinet的長尾延時的影響。SILK的排程策略如下:
a. SILK確保Flush足夠快,即Flush的優先順序最高。從而為記憶體中的write-buffer接受客戶端的請求留下足夠的空間。Flush的速度是直接影響客戶端的長尾延時問題。
b. SILK 將L0 -> L1 Compaction的速度放在第二優先順序,確保L0不會達到它的容量上限,從而保證Flush能夠有足夠的空間完成操作。
c. SILK 將Higher Level Compaction的優先順序設定為最低,因為這些Compactions的目的是為了維持LSM的形狀,並不會短期內對Client的長尾延時造成影響。
3.2 SILK 詳細實現
3.2.1 按照概率分配I/O頻寬
SILK會持續監控 Client操作被分配的I/O頻寬,並且分配可用的頻寬給到internal ops。
根據客戶端 qps的波動情況,配置對應的監控粒度,用來決定為internal ops分配頻寬的比例。當前SILK的實現配置的監控粒度是10ms。
具體配置方式如下:
T B/s : 表示在LSM 架構的KV儲存中 ,總的I/O頻寬
C B/s: SILK 監控的Client操作佔用的I/O頻寬
則internal ops可用的頻寬是:I = T - C - µ B/s,其中 µ 可以理解為是一個小的buffer。
為了靈活得調整I/O頻寬,SILK使用了Rocksdb標準的Rate Limiter, 同時SILK 為Flushing和 L0->L1 Compaction,也維護了一個最小的可配置的I/O頻寬 時間間隔。
3.2.2 優先順序排程 和 可搶佔的 Internal ops設計
SILK 維護了internal work的執行緒池。當一個Flush任務或者一個Compaction任務處理完成之後,執行緒池會根據當前LSM 不同層待Compaction的量以及記憶體中write-buffer的狀態 來判斷是否需要排程更多的執行緒進行對應的internal (Flush 或者 L0 Compaction)任務的排程處理。
接下來主要介紹一下SILK 維護的兩個執行緒池:一個 為flushing的 high-priority 執行緒池,另一個低優先順序的Compactions執行緒池。
Flushing 在所有的internal ops中優先順序最高。所以它有自己專有的執行緒池,並且有許可權搶佔其他任何低優先順序的I/O頻寬。Flushing的最小頻寬需要能夠滿足從immutable memtable flush的速度快於active memtable的更新速度。當前 實現的SILK 允許記憶體中存在兩個memtable(一個是接受更新的active memtable,另一個只讀的immutable memtable) 和一個flush執行緒,可以通過設定memtable的個數來讓client ops低時延維持時間更久。
ps : 這裡提到的memtable 也是之前描述的write-buffer,都是LSM在記憶體中使用跳錶維護的有序元件。
L0–》L1 Compaction。L0->L1的compaction 主要是為了保證L0有足夠的空間來接受Flush的結果。SILK的實現中 這個internal ops並沒有專用的執行緒進行排程,而是在需要進行L0–>L1的Compaction時 從Compaction pool中隨機選擇一個執行緒搶佔 進行L0->L1的處理。
這裡有必要說明一下,L0–>L1 的compaction這麼重要,為什麼我們不實用多執行緒排程,從而加快這個過程?
因為L0的sst檔案之間存在key的overlap,為了保證大於L0的層內sst檔案之間不存在overlap,則需要保證L0的檔案處理的原子性,所以這裡只能使用一個執行緒進行排程。
而Rocksdb原生的實現中為了減少L0的檔案重疊度,也會排程L0–>L0的compaction,這裡SILK仍然會沿用rocksdb的邏輯(SILK是基於rocksdb 5.7版本進行的改造),只是會將L0–>L0的Compaction 看作L0–>L1 一樣的優先順序。
Higher Level Compactions。更高層的Compaction 擁有排程的優先順序最低。比如L1–>L2進行Compaction的過程中任務被L0–>L1的Compaction需求搶佔,這個時候SILK會丟棄掉當前正在做的Compaction任務,不過實際的測試並沒有發現這個操作對效能產生非常明顯的影響。
SILK控制了總的KV store的I/O頻寬,通過在Compaction執行緒池中排程更低優先順序的 Compactions來完成這一過程。比較有趣的是 SILK能夠根據 Compactions 任務的緊急程度進行對應I/O頻寬的分配,從而能夠降低整個LSM tree發生Latency Spike的概率 。
4. 效能資料
Nutanix 的workload是:write:read:scan的比例 – 57:41:2 ,客戶端穩定輸出的峰值20k/s
可以看到 SILK 能夠在很長的一段時間保證客戶端p99維持在1ms以下,且吞吐能夠和客戶端接近,相比於其他的系統則 比較有優勢。
在Synthetic的workload下,擁有較高的寫比例,同時 peek也由原來的20k/s 逐漸下降到 每隔10s 20k, 每隔50s 20k,每隔100s 20k, 更長時間之後來一次洪峰20k
可以看到silk 的延時會隨著洪峰持續的時間而逐漸增加,如果洪峰是間斷性來臨(比如 洪峰維持10s, 50s, 100s),則SILK能夠提供非常穩定且較低的延時。而因為洪峰的持續時間越長,待Compaction的量累積越多,Higher level compactions的影響越大(已經無法避免得排程更高層的Compaction)。
不過還是能夠看到silk是有能力支撐突然而來的流量洪峰,保證吞吐的前提下並維持在一個較低的延時上。
50% read 和 50% write 場景下,這裡SILK 將自己和前兩種不同的 internal限速策略進行對比,比如 :
第一行藍色的表示 動態分配internal IO的頻寬,關閉 支援優先順序搶佔的排程策略。這個前期能夠維持一個平穩的延時,但是隨著 Compaction 量的累積,後期仍會降低高優先順序的操作。
第二行只開啟 優先順序搶佔的策略,關閉動態分配IO頻寬的策略。因為 internal ops能夠進行不同優先順序之間的排程搶佔,但是沒有辦法統籌整體的IO頻寬,從而導致後期 Higher Level 的Compaction量增加,無法增大I/O頻寬,使得internal ops的排程和client的 排程出現衝突。
第三行 SILK 是融合了以上兩種的策略,可以看到整體能夠維持在一個非常平穩的延時和吞吐上。
5. 業界相關LSM優化的展望
在這裡 ,SILK介紹了三個方向的LSM相關的優化 以及 近些年業界已有的優化方案,非常有參考價值。
5.1 降低Compaction開銷方向
5.2 Compaction變種 或者 實現相關的代替方案
5.3 在LSM 的資料結構和相關演算法上的一些優化
LSM 的作為新生代儲存引擎的基礎架構,優異的寫吞吐,天然支援的冷熱分離架構下提供足量的讀的優化。
有得必有失,Compaction的 資料回收和 merge sort帶來的I/O 排程 挑戰讓後來者想要不斷去征服這座效能高峰,為引擎界引入足以和B樹相媲美的經典架構。
學無止境,不過還好有高手指路,能夠發現如此精彩的儲存世界,嘆之有幸!!!
相關文章
- LSM merge的過程
- LSM(Log Structured Merge Trees ) 筆記Struct筆記
- group by排序,derived_merge優化的坑排序優化
- MySQL 優化之 index_merge (索引合併)MySql優化Index索引
- webpack系列-優化Web優化
- 《金三銀四面試系列》— jvm與效能優化面試JVM優化
- MySQL系列:效能優化MySql優化
- Oracle效能優化-SQL優化(案例三)Oracle優化SQL
- 'This NSPersistentStoreCoordinator has no persistent stores 報錯
- PostgreSQL DBA(173) - pgAdmin(Network latency)SQL
- Go 高效能系列教程之三:編譯器優化Go編譯優化
- 首屏優化系列(二)優化
- 首屏優化系列(一)優化
- Flutter持久化儲存之key-value儲存Flutter持久化
- AirNet備用ATC系統維護筆記(三)AI筆記
- TiDB 查詢優化及調優系列(三)慢查詢診斷監控及排查TiDB優化
- MySQL 優化三(優化規則)(高階篇)MySql優化
- DelayQueue系列(三):持久化方案持久化
- iOS效能優化系列篇之“列表流暢度優化”iOS優化
- iOS效能優化系列篇之“優化總體原則”iOS優化
- 前端優化系列之目錄前端優化
- TiDB 查詢優化及調優系列(一)TiDB 優化器簡介TiDB優化
- SSE影像演算法優化系列十八:三次卷積插值的進一步SSE優化。演算法優化卷積
- 川大主用ATC系統維護筆記(三)筆記
- 百度App網路深度優化系列《一》DNS優化APP優化DNS
- Challenges preventing us moving to 64 bit transaction id (XID)?
- 並查集系列之「思路優化」並查集優化
- 【electron-playground系列】打包優化之路優化
- 《java學習三》jvm效能優化-------調優JavaJVM優化
- Mysql優化系列之——優化器對子查詢的處理MySql優化
- MySQL優化學習手札(三)MySql優化
- Go工程管理 19 | 效能優化:Go 語言如何進行程式碼檢查和優化?Go優化行程
- VuePress 部落格之 SEO 優化(三)標題、連結優化Vue優化
- Oracle優化案例-view merge與coe_load_sql_profile固定執行計劃(十五)Oracle優化ViewSQL
- JS進階系列 --- ajax請求優化JS優化
- 【推薦】Java效能優化系列集錦Java優化
- 推薦:Java效能優化系列集錦Java優化
- MySQL系列6 - join語句的優化MySql優化