Presto+騰訊DOP(Alluxio)在騰訊金融場景的落地實踐
一、背景和架構演進思考
近十年大數據發生了很大變化,從一開始的Hadoop滿足數據簡單可查可用,到現在對數據分析的極速OLAP需求,大家對數據探索的性能要求越來越高。同時數據量在近幾年也是不斷增長,降本增效成為用戶普遍的需求。
雖然這些年SSD不管是性能還是成本都獲得了長足的進步,但是在可見的未來5年,HDD還是會以其成本的優勢,成為企業中央存儲層的首選硬件,以應對未來還會繼續快速增長的數據。
如下圖是一次OLAP分析讀取ORC數據的情況,灰色豎條表示OLAP分析需要讀取的三列數據在整個文件中的可能的位置分布 ,也就是只會讀ORC的Stripe文件中某一小部分數據。
可以看到整個讀取過程是一個碎片化的IO過程,所以就存在使用低成本HDD解決存儲低成本需求和OLAP分析性能越來越快的矛盾。基于此也引發了我們的一些思考。
在整個OLAP過程中有很多常見架構的選擇,比如有一些公司會選擇直連中央存儲架構,這種架構存在兩方面的問題:
- HDD磁盤讀取尋道會存在并發瓶頸,另外就是碎片化IO尋道耗時較長。
- 金融科技白天會運行很多算法類的和畫像類任務不斷運行,使用中央存儲IO負載較高,OLAP分析只要有一個Task沒有返回結果就會引發長尾效應,導致整個分析任務都會卡住。
另一種經常選擇的架構是獨立OLAP存儲計算架構,也就是把數據抽取到一份獨立的存儲,然后在上面做OLAP分析,但是這種方案也在不斷的受到挑戰:
- 第一點就是數據的邊界問題:在這樣的一種方案下數據的邊界是沒有辦法靈活去調整的,比如一開始用戶要求這一份數據只存三個月,但是某一天因為有一些特殊的場景需要對比去年或早期數據,那么需要更長的時間范圍,這時候是沒有辦法快速靈活的調整數據的可訪問范圍。
- 第二點是數據一致性問題,我們在金融行業經常被挑戰,畢竟增加了一次的數據的復制,必然會存在數據一致性的問題;如果發生數據的回溯,對歷史的數據的重新生成,會進一步增加數據不一致的概率。
- 第三點就是數據安全的問題,這也是金融行業最常談的,把數據抽取一份到獨立存儲,那每個庫表的權限怎么管理是需要考慮的。
重新思考以上問題,其實背后需求是冷熱存儲的需求,受限越來越快OLAP分析我們需要的是一份能夠被OLAP獨享的一份數據副本,而且它最好是SSD存儲,滿足更高的性能要求;其次不引入額外的數據管理成本,只管理數據生命周期而不用關注權限和安全。因此在這樣背景下我們進行了一些探索,也就是今天要分享的主題,即presto+騰訊DOP(Alluxio)來解決我們剛才所提出了幾個問題。
二、Presto+騰訊DOP(Alluxio)架構
Alluxio一般用來做緩存加速,大部分情況下是一種以co-located方式跟節點做混合部署,提高I/O本地性,用覆蓋20%數據需滿足80%的查詢需求,去保證高頻請求的加速,另外根據節點多副本情況動態調整,滿足更高的數據查詢負載。
在騰訊金融科技,我們傾向是把Alluxio當做HDFS的SSD副本來使用,與底層IO進行隔離,因此是不要求co-located部署,以遠程訪問為主,那么這種情況就需要更大存儲來獨立擴縮容,盡可能多的緩存用戶需要的那部分數據,并且在Alluxio中配置單副本就基本能滿足了我們現在的查詢并發壓力。
在我們整個架構選型中涉及幾個技術決策點:
- 我們選擇presto主要考慮到是他的調度的模型,他能夠根據每個節點的狀態去分配不同的split,相比于靜態模型會有更強的容錯性,可以減少一些長尾的效應;還有他的本地優先級對列,能夠比較好的去平衡大查詢和小查詢之間的矛盾,會根據每個查詢執行時長區分的不同的等級,在越短時間內能夠更快的完成。另外一點我們選擇Presto是因為我們有一些存量的技術基礎,包括我們數據平臺部做了一些技術積淀。
- 我們引入SuperSQL主要是考慮兩點:第一點主要是SuperSQL基于Calcite統一語法,能夠無縫的把Presto的SQL查詢轉到Spark上,這樣可以在一些大查詢場景下緩解Presto計算資源壓力;第二點是在Presto落地過程中發現在Left Join場景下對右表的帶有null值的列做count distinct 很容易出現數據傾斜,因此使用Calcite對distinct今進行展開解決count distinct的問題;
- 引入騰訊DOP(Alluxio)主要因為:第一點我們是想利用Alluxio的LRU緩存策略來實現數據的生命周期管理;第二點獨立部署Alluxio可以利用ssd加速我們OLAP的查詢請求;第三點是利用Alluxio數據CACHE預加載策略,通過olap引擎側主動發起預加載查詢, 讓alluxio被動觸發預加載。
在這種架構選擇下我們同樣會會面臨幾個挑戰:
挑戰一就是選擇Alluxio CACHE模式如何保障ALLUXIO中數據穩定性?
Presto Client端在發起數據讀取時會查詢Alluxio Worker中是否緩存所需要的數據塊,如果發現數據并沒有在Alluxio,就會去底層的HDFS把數據讀回來,需要多少數據就讀多少數據,數據讀回來之后先返回給Presto側滿足后續的計算,同時也會發送異步的Cache quest的請求緩存命令到Alluxio Worker,如果Worker節點內存空間不夠,則會根據配置清理策略淘汰一部分數據,比如LRU就會把最早的那部分數據把它淘汰出去,然后把新的數據塊緩存進來。在這個過程中如果用戶突然發起一個意外的超大范圍查詢或歷史數據訪問觸發大量的block驅逐,導致我們經常用到的那部分數據都不會被緩存。
為了解決這個問題,首先我們在Presto中了對Alluxio模塊進行擴展實現旁路直連功能,對Presto查詢請求進行判斷,對于大范圍查詢直接繞過讀取Alluxio的流程,直接讀取HDFS。這個模塊我們做了庫表白名單和庫表范圍配置功能,構建橫向和縱向的穩定性護城河。
在白名單里我們限定哪些庫表能夠訪問Alluxio,避免預期之外的查詢訪問觸發Alluxio大面積的數據驅逐;另外通過時間范圍縱向約束,限制什么時間范圍內數據才會走Alluxio查詢。
但僅通過上述方法還是不夠,因為真正業務上很難確定什么表應該要緩存什么樣的時間,而且用戶的查詢需求跟現在實際的緩存是否能夠匹配也不能確定。因此我們后面又做了進一步的優化,繼續結合用戶的歷史的查詢去計算出最優的存儲范圍。
這個問題可以抽象為一下模型:
- 每個主題表有不同的使用頻次和用戶數,我們定義了一個價值分的模型=使用頻次*log(用戶數+e) 。
- 每個主題表根據每個sql的查詢范圍會有:50分位、70分位....99分位的范圍值(天),不同分位值對應不同存儲需求。
- 求在一個固定的存儲空間范圍內最大價值分的每個主題表的保存范圍組合。
但這個問題是不能直接計算的,因為假設查詢范圍有6種可能,表有100個,那么這里的組合可能性高達6^100,因此我們從數據主題價值分和存儲命中率兩個維度進行分組,同一個分組的主題表采用同一個分位值這樣就將計算量降低到了6^9,這樣就能夠計算充分利用Alluxio的存儲,又能達到最佳用戶價值。
我們查詢接入層會每天計算過去14天最優庫表范圍,然后加載到Presto的庫表白名單中控制數據的訪問,通過這種方式我們整體緩存命中率能夠達到98%。
挑戰二是如何提升騰訊DOP(Alluxio)的存儲的擴展性?
我們把Alluxio當做存儲層存在獨立擴展的問題,在整個方案落地的過程中會有一些異構的存儲,比如一些機器的SSD存儲比較大,一些機型SSD存儲比較小,如何讓存儲能夠被充分利用是我們需要考慮的問題。
在Allluxio已有的策略中:
- RoundRobinPolicy和DeterministicHashPolicy都屬于平均策略,將請求平均分配給所有Worker, 由于小容量的worker能夠處理請求低于大容量,因此其上的數據淘汰率更高。
- MostAvailableFirstPolicy策略,可能會導致大容量worker容易成為數據加載熱點,而且因為所有 worker存儲最終都會達到100%,所以滿了之后這個策略也就是失去意義了。
針對這個問題,騰訊內部設計了基于容量的存儲分配策略CapacityBaseRandomPolicy的策略,也貢獻給了Alluxio社區。CapacityBaseRandomPolicy策略在隨機策略的基礎上,基于不同worker的容量給予不同節點不同的分發概率。這樣容量更大的worker就會接收更多的請求,配合不同worker上的參數調整,實現了均衡的數據負載。
這個策略在內部上線初期也達到了在預期的效果,不同worker根據其自身容量來接收多少請求存儲多大數據量,這樣就保證每個worker上淘汰率是相同的,數據得到了比較好的保留。后面我們又演化了優化版的CapacityBaseDeterministicHashPolicy的策略,主要考慮到在初期加載的時候,Presto對同一份數據同時發送多個請求,因為randon的策略分到不同的worker,導致的就在多個worker上在某一時刻會并發多個加載同一份數據,對這種情況做了優化。
這個功能上線后,內部又做了實際的測試,基于歷史的查詢做了回放,回放了兩個場景還是我們最開始關注的兩個點:IO隔離和SSD加速。
我們利用五個并發在閑時和忙時兩個時段進行測試。
閑時階段我們選了周末的某下午,在整個HDFS集群比較閑的時候進行,在這個測試場景下,如果有Alluxio 90分位的耗時是16,沒有Alluxio則90分位耗時則達到27,整體性能提升68%,這個加速來源是Alluxio使用的SSD硬盤。
忙時階段測試我們選擇了一個工作日的早晨,這個測試下有Alluxio 90分位耗時為18,相對閑時階段并沒有太大差異,但是如果沒有Alluxio 90分位耗時達到了71,主要的原因是在這個時間段在我們的HDFS集群中央存儲會有很多的計算IO負載,導致它的IO波動會非常大,根據長尾理論查詢的耗時就會拉的非常長,這塊加速的原因就是因為SSD加速加上IO隔離的效果。
因為我們的計算都是遠程讀,計算和存儲是完全分離的狀態,整個計算節點是完全對等的,所以后面我們又進一步做了探索,基于內部峰巒K8S進行潮汐調度,白天將YARN的空閑計算資源動態的擴容到Presto集群來加速作業執行,晚上再把資源返還給YARN集群跑離線任務。這樣就把我們整個集群的資源充分利用起來,提升OLAP引擎的性能。
三、落地過程中的優化實踐
這一小節主要分享我們再落地過程中遇到的兩個問題及優化實踐:
presto在orc上的優化實踐
Presto有兩種類型的stage:source stage(數據讀取,涉及底層Alluxio及HDFS的IO操作)和fixed stage(其他的Agg、Join等操作),source stage的有效并發取stripe數量和split 數量最小值, fix stage的并發則是由task.concurrency參數指定。本文圍繞source stage對ORC的并發優化展開。
ORC一個文件包含多個stripe,每個Stripe包含多個Column,可以理解為先按行進行分組,然后組內按照列進行存儲。如右下圖示意ORC文件中有3個stripe文件,默認情況initial_split_size是32M,max_split_size是64M,實際上split_size并不等同于并發量,主要原因是Presto計算并發時,如果一個split跨了兩個column讀取是無意義的,否則無法獨立計算,所以并發計算邏輯是判斷split是否包含stipe的開始位置,包含stipe的開始位置才是有效的split。
在ORC寫入邏輯中有個參數是orc.stripe.size,用于控制寫入過程中內存的buffer,buffer,滿了就會觸發flush,壓縮生成一個stripe。這種方式可能會導致兩個極端:
- 行數過多,表的字段比較少情況Presto并發會比較低;
- 行數過少,表的字段卻很多或內容較大,導致IO次數過高,效率低或觸發合并讀取。
Presto中的合并讀是對IO讀取的優化,合并機制是由hive.orc.tiny-stripe-threshold參數控制,如果stripe的大小小于參數值(默認8M)則完全讀取整個stripe的所有列,如果文件都小于這個值就更是如此。在測試過程中遇到一種情況是一個簡單的count(*)的查詢,由于觸發了合并讀讀取了幾百G的文件(PS: 在有些TPCDS的測試中生成的文件都是小于8M的,這種情況也會失去列式存儲減少IO的效果,導致性能大幅降低)。
如右下圖實際的case中,每一個stripe都有5000行,讀一個column需要加載幾百G的IO,完全失去了列式存儲的優勢。這里我們線上的優化點是結合SSD的特性把參數調整為1MB,避免過度合并IO,減少Alluxio的IO吞吐和網絡開銷,另外一點我們再思考能否對ORC文件合并進行更合理的控制。
由于stripe size內存buffer跟行數的對應關系是很難計算的,跟表的字段及字段包含的大小有關,所以同樣的64M的stripe size,如果只有5列那么可以容納500w行,如果有500列的寬表那么可能只有1w行,這樣也很難與數倉同學溝通,那么stripe size的參數設置為多大就非常難以決策了。也正基于此我們再ORC中增加了一個參數:orc.stripe.row.count (對應社區Issue:ORC-1172),實現思想就是在stripe.size的基礎上增加行數的約束,這樣就可以把stripe.size參數設置大一些,然后設置相對合理的row.count參數,這樣就可以滿足OLAP的查詢需求了。
騰訊DOP(Alluxio) master的優化
在一些對Alluxio IO場景要求比較高的場景,比如漏斗查詢,會發現IO的耗時會比較高,定位發現在Alluxio的master中RPC排隊比較嚴重,然后使用Kona-profiler觀察發現大量未被釋放的Rocksdb的Finalizer引用,占用了26GB的內存,影響了GC的回收。
基于這個問題我們去分析了Alluxio master的元數據,它的元數據包括兩塊:
- inode: 目錄和文件信息。
- block: 數據塊元信息和location信息。
因為數據塊的元信息的量是會隨著時間的增長是會持續增長,但location的信息是相對穩定的,而且它是變化比較快的一部分,因此我們考慮把數據塊元信息還保留在Rocksdb,另外block的location信息放在內存里面。通過這項優化QPS從原來2.5萬提升到了6.5萬,master的RPC情況也得到了大幅緩解(PR 15238)。
四、總結與展望
這是一次非常成功的跨 BG,跨團隊協作,快速有效的解決騰訊 Alluxio(DOP) 落地過程中的問題,順利使得騰訊 Alluxio(DOP) 在 金融業務場景落地。
在整個Alluxio的優化過程中,不斷對IO、CPU和網絡進行循環優化,先做了一輪io的優化,然后發現cpu成為瓶頸,也是我們當下面臨的最大的問題,很多的查詢都會跑滿CPU,怎么優化CPU也是我們下一個要考慮的問題,我們看到今年9月Meta發布的Velox的論文,用C++重寫了Presto的worker,在內部測試集中取得很好效果,這也是后面我們要去探索的地方。最后IO和CPU優化差不多的時候,就會發現網絡可能會存在性能問題,那么只能進行架構調整,然后開始第二輪的優化。
后續我們將針對Presto結合HUDI查詢進行更多的探索。
在開放性上,我們會接入更多的業務場景,來提升我們的業務價值。