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