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