背景
今天我們來聊一聊flink中狀態rescale的效能優化。我們知道flink是一個支援帶狀態計算的引擎,其中的狀態分為了operator state和 keyed state兩類。簡而言之operator state是和key無關只是到operator粒度的一些狀態,而keyed state是和key繫結的狀態。而Rescale,意味著某個狀態節點發生了併發的縮擴。在任務不修改併發重啟的情況下,我們只需要按照task,將先前job的各個併發的state handle重新分發處理下載遠端的持久化的state檔案即可恢復。而發生rescale時,狀態的資料分佈將發生變化,因此存在一個reshuffle的過程,那麼我們就來看看這個rescale的實現是怎麼做的,以及其問題和優化手段。
Rescale的實現
2017年社群有一篇部落格就比較深入的介紹了Operator 和 keyed state的rescale的實現,感興趣的話可以去了解下。
這兩張圖對比了是否基於keyGroup來劃區的一個差別,社群中的版本使用的是基於keygroup的版本實現的,可以看到可以減少對於資料的random的訪問。但是從B中我們看到,以rescale後的subtask為例:
- subtask-0: 需要將原先subtask-0的dfs檔案下載後將KG-3的資料剔除掉。這裡需要剔除的原因是: 雖然我們任務啟動後由於keyshuffle的原因,subtask-0不會再接收到KG-3的資料,但是後續如果繼續做checkpoint,會導致這部分資料重新被上傳到DFS檔案中,而如果繼續發生rescale,就可能導致和其他subtask-1上的KG-3的資料發生衝突導致資料問題
- subtask-1: 需要download原先subtask-0和subtask-1的資料dfs檔案,並將subtask-0中的KG-1和KG-2的資料刪除,以及原先subtask-1中的 KG-5 和 KG-6刪除,並將其匯入到新的RocksDB例項中。
因此我們可以總結出rescale的大致流程中,首先會將當前task所涉及的db檔案恢復到本地,並從中挑選出屬於當前keygroup的資料重新構建出新的db。
從理論上分析,在不同的併發調整場景下,其rescale的代價也不盡相同
併發翻倍
1.5倍擴併發
併發減半
接下來,我們在程式碼中確認相關的邏輯(程式碼基於Flink1.15版本)。
根據stateHandle元資訊判斷是否是rescale
我們可以看到當restoreStateHandles的數量大於1,或者stateHandle的keyGroupRange和當前task的range不一致時就是rescale的過程
在不是rescale的場景下,恢復的流程只需要將相應IncrementalRemoteKeyedStateHandle
對應的檔案下載到本地或者是直接使用local recovery中的 IncrementalLocalKeyedStateHandle
所對應的本地檔案的目錄,直接執行 RocksDB.open()
就可以將db資料恢復。
initialDB
首先根據keygroup的重疊比較,挑選出和當前keygroup有最大重疊範圍的stateHandle作為initial state handle。這樣的好處是可以儘可能利用最大重疊部分的資料,減少後續資料遍歷的過程。在挑選出initial state handle 建立db之後,首先需要將db中不屬於當前的task的keygroup的資料進行遍歷刪除。
因為flink中儲存的keyed state的資料已經按照keygroup作為字首作為排序,所以只需要刪除頭部和尾部的資料即可,這樣就不用遍歷全量的資料。
在當前的deleteRange的實現中是依賴遍歷db,通過writeBatch的方式進行批量執行刪除,這種方式當需要刪除的key的基數較大時會比較耗時,並且都會觸發io和compaction的開銷,而rocksdb提供了deleteRange的介面,可以通過指定start和end key來進行快速的刪除,經過測試下來基本只要ms級別就可以完成。參考 FLINK-21321
Bulk load
在完成base db裁剪之後,就需要將其他db的資料匯入到base db中,目前的實現還是通過writeBatch來加速寫入
在 FLINK-17971 中作者提供了sst ingest 寫入的實現,本質上是利用rocksdb 的sst writer的工具,通過sst writer能直接構建出sst 檔案,避免了直接寫的過程中的compaction的問題,然後通過 db.ingestExternalFile
直接將其匯入db中。實際測試的過程中這樣的寫入效能有2-3倍的提升。
Rescale的優化應該迭代優化了很多次,最開始的實現應該是將所有的statehandle的資料download下來,將其遍歷寫入新的db,在 FLINK-8790 中首先將其優化成 base db + delete Range + bulk load的方式,後續的兩個pr又通過Rocksdb提供的deleteRange + SSTIngest 特性加速。雖然這些優化應用上只有rescale的提速很明顯,但是當我們遇到key的基數非常大時,就會出現我們遍歷原先的db next呼叫和 寫入的耗時也非常的大,因此rescale的場景可能還需要繼續優化。
關於RocksDB中deleteRange和SST Ingest功能筆者也做了一些研究,在後續的文章中會陸續更新出來,敬請期待
參考
https://blog.csdn.net/Z_Stand/article/details/115799605 sst ingest 原理
http://rocksdb.org/blog/2017/02/17/bulkoad-ingest-sst-file.html
https://rocksdb.org/blog/2018/11/21/delete-range.html delete range原理