小米 A/B 實驗場景基于 Apache Doris 的查詢提速優(yōu)化實踐

導讀:?Apache Doris 是小米集團內(nèi)部應用最為廣泛的 OLAP 引擎之一,本文主要從數(shù)據(jù)的角度分析 A/B 實驗場景查詢的性能現(xiàn)狀,探討基于 Apache Doris 的性能優(yōu)化的解決方案。經(jīng)過一系列基于 Doris 的性能優(yōu)化和測試,A/B 實驗場景查詢性能的提升超過了我們的預期。希望本次分享可以給有需要的朋友提供一些參考。

作者|小米集團大數(shù)據(jù)工程師 樂濤

一、業(yè)務背景

A/B實驗是互聯(lián)網(wǎng)場景中對比策略優(yōu)劣的重要手段。為了驗證一個新策略的效果,需要準備原策略A和新策略B兩種方案。 隨后在總體用戶中取出一小部分,將這部分用戶完全隨機地分在兩個組中,使兩組用戶在統(tǒng)計角度無差別。將原策略A和新策略B分別展示給不同的用戶組,一段時間后,結(jié)合統(tǒng)計方法分析數(shù)據(jù),得到兩種策略生效后指標的變化結(jié)果,并以此判斷新策略B是否符合預期。

小米A/B實驗平臺是一款通過A/B實驗的方式,借助實驗分組、流量拆分與科學評估來輔助完成科學的業(yè)務決策,最終實現(xiàn)業(yè)務增長的一款運營工具產(chǎn)品。其廣泛的應用于產(chǎn)品研發(fā)生命周期中各個環(huán)節(jié):

本文主要從數(shù)據(jù)的角度分析A/B實驗場景查詢的性能現(xiàn)狀,探討一下性能優(yōu)化的解決方案。

二、數(shù)據(jù)平臺架構(gòu)

A/B實驗平臺的架構(gòu)如下圖所示:

平臺使用的數(shù)據(jù)主要包含平臺自用的實驗配置數(shù)據(jù)、元數(shù)據(jù),以及業(yè)務方上報的日志數(shù)據(jù)。

由于業(yè)務方引入 SDK,并與分流服務進行交互,日志數(shù)據(jù)中包含其參與的實驗組 ID 信息。

用戶在實驗平臺上配置、分析、查詢,以獲得報告結(jié)論滿足業(yè)務訴求。

鑒于AB實驗報告各個業(yè)務方上報數(shù)據(jù)的鏈路都大體類似,我們就拿頭部業(yè)務方廣告業(yè)務舉例

數(shù)據(jù)流程如下圖所示:

整個數(shù)據(jù)鏈路并不復雜,日志數(shù)據(jù)傳入后,經(jīng)過必要的數(shù)據(jù)處理和清洗工作進入 Talos(小米自研消息隊列),通過 Flink 任務以明細數(shù)據(jù)的形式實時寫入到 Doris 表中,同時 Talos 數(shù)據(jù)也會同步到 Hive 表進行備份,以便問題排查和數(shù)據(jù)修復。

出于對高效寫入以及字段增減需求的考慮,Doris 明細表以 Duplicate 模型來建模:

CREATE TABLE `dwd_xxxxxx` (

? `olap_date` int(11) NULL COMMENT "分區(qū)日期",

? `user_id` varchar(256) NULL COMMENT "用戶id",

? `exp_id` varchar(512) NULL COMMENT "實驗組ID",

? `dimension1` varchar(256) NULL COMMENT "",

? `dimension2` varchar(256) NULL COMMENT "",

? ......

? `dimensionN` bigint(20) NULL COMMENT "",

? `index1` decimal(20, 3) NULL COMMENT "",

? ......

? `indexN` int(11) NULL COMMENT "",

) ENGINE=OLAP

DUPLICATE KEY(`olap_date`, `user_id`)

COMMENT "OLAP"

PARTITION BY RANGE(`olap_date`)

(

PARTITION p20221101 VALUES [("20221101"), ("20221102")),

PARTITION p20221102 VALUES [("20221102"), ("20221103")),

PARTITION p20221103 VALUES [("20221103"), ("20221104"))

)

DISTRIBUTED BY HASH(`user_id`) BUCKETS 300

;

三、數(shù)據(jù)現(xiàn)狀分析

在提速之前,小米A/B實驗平臺完成實驗報告查詢的 P95 時間為小時級,實驗報告使用數(shù)據(jù)的方式存在諸多的性能問題,直接影響業(yè)務部門做運營和決策的效率。

3.1 報告查詢基于明細

當前報告查詢的數(shù)據(jù)來源為明細表,而明細表的數(shù)據(jù)量巨大:

而且,實驗報告的查詢條件中時間范圍常常橫跨多天?;跉v史查詢報告統(tǒng)計,查詢條件中時間范圍大于一天的報告占比69.1%,具體的時間跨度占比分布如下:

明細數(shù)據(jù)的巨大掃描量給集群帶來了不小的壓力,且由于報告查詢存在并發(fā)以及 SQL 的拆分,如果一個 SQL 請求不能快速的返回結(jié)果釋放資源,也會影響到請求的排隊狀況。因此在工作時間段內(nèi) Doris 集群BE節(jié)點 CPU 負載狀況基本是持續(xù)滿載,磁盤 IO 也持續(xù)處于高負荷狀態(tài),如下圖所示:

BE節(jié)點CPU使用率

BE節(jié)點磁盤IO

個人思考

當前報告所有查詢基于明細數(shù)據(jù),且平均查詢時間跨度為 4 天,查詢掃描數(shù)據(jù)量上百億。

由于掃描數(shù)據(jù)量級大,計算成本高,給集群造成較大壓力,導致數(shù)據(jù)查詢效率不高。

如果通過對數(shù)據(jù)進行預聚合處理,控制 Scan Rows 和 Scan Bytes,減小集群的壓力,查詢性能會大幅提升。

3.2 字段查詢熱度分層分布

由于之前流程管控機制相對寬松,用戶添加的埋點字段都會進入到明細表中,導致字段冗余較多。

統(tǒng)計歷史查詢報告發(fā)現(xiàn),明細表中常用的維度和指標只集中在部分字段,且查詢熱度分層分布:

參與計算的指標也集中在部分字段,且大部分都是聚合計算(sum)或可以轉(zhuǎn)化為聚合計算(avg):

個人思考

明細表中參與使用的維度只占54.3%,高頻使用的維度只占15.2%,維度查詢頻次分層分布。

數(shù)據(jù)聚合需要對明細表中維度字段做取舍,選擇部分維度進行上卷從而達到合并的目的,但舍棄部分字段必然會影響聚合數(shù)據(jù)對查詢請求的覆蓋情況。而維度查詢頻次分層分布的場景非常適合根據(jù)維度字段的熱度做不同層次的數(shù)據(jù)聚合,同時兼顧聚合表的聚合程度覆蓋率。

3.3 實驗組ID匹配效率低

當前明細數(shù)據(jù)的格式為:

明細數(shù)據(jù)中的實驗組ID以逗號分隔的字符串形式聚攏在一個字段中,而實驗報告的每條查詢語句都會使用到exp_id過濾,查詢數(shù)據(jù)時使用LIKE方式匹配,查詢效率低下。

個人思考

將實驗組ID建模成一個單獨的維度,可使用完全匹配代替LIKE查詢,且可利用到Doris索引,提高數(shù)據(jù)查詢效率。

將逗號分隔的實驗組ID直接打平會引起數(shù)據(jù)量的急劇膨脹,因此需要設計合理的方案,同時兼顧到數(shù)據(jù)量和查詢效率。

3.4 進組人數(shù)計算有待改進

進組人數(shù)查詢是實驗報告的必查指標,因此其查詢速度很大程度上影響實驗報告的整體查詢效率,當前主要問題如下:

當進組人數(shù)作為獨立指標計算時,使用近似計算函數(shù)APPROX_COUNT_DISTINCT處理,是通過犧牲準確性的方式提升查詢效率。

當進組人數(shù)作為復合指標的分母進行計算時,使用COUNT DISTINCT處理,此方式在大數(shù)據(jù)量計算場景效率較低。

個人思考

AB實驗報告的數(shù)據(jù)結(jié)論會影響到用戶決策,犧牲準確性的方式提升查詢效率是不可取的,特別是廣告這類涉及金錢和業(yè)績的業(yè)務場合,用戶不可能接受近似結(jié)果。

進組人數(shù)使用的COUNT DISTINCT計算需要依賴明細信息,這也是之前查詢基于明細數(shù)據(jù)的重要因素。必須為此類場景設計新的方案,使進組人數(shù)的計算在保證數(shù)據(jù)準確的前提下提高效率。

四、數(shù)據(jù)優(yōu)化方案

基于以上的數(shù)據(jù)現(xiàn)狀,我們優(yōu)化的核心點是將明細數(shù)據(jù)預聚合處理,通過壓縮數(shù)據(jù)來控制Doris查詢的Scan Rows和Scan Bytes。與此同時,使聚合數(shù)據(jù)盡可能多的覆蓋報告查詢。從而達到,減小集群的壓力,提高查詢效率的目的。

新的數(shù)據(jù)流程如下圖所示:

整個流程在明細鏈路的基礎上增加聚合鏈路,Talos數(shù)據(jù)一方面寫入Doris明細表,另一方面增量落盤到Iceberg表中,Iceberg表同時用作回溯明細數(shù)據(jù)以及生成聚合數(shù)據(jù),我們通過工場Alpha(小米自研數(shù)據(jù)開發(fā)平臺)的實時集成和離線集成保證任務的穩(wěn)定運行和數(shù)據(jù)的一致性。

4.1 選取高頻使用維度聚合

在生成數(shù)據(jù)聚合的過程中,聚合程度與請求覆蓋率是負相關的。使用的維度越少,能覆蓋的請求就越少,但數(shù)據(jù)聚合程度越高;使用的維度越多,覆蓋的請求也越多,但數(shù)據(jù)粒度就越細,聚合程度也越低。因此需要在聚合表建模的過程中取得一個平衡。

我們的具體做法是:拉取歷史(近半年)查詢?nèi)罩具M行分析,根據(jù)維度字段的使用頻次排序確認進入聚合表的優(yōu)先級。在此基礎上得出聚合表的覆蓋率和數(shù)據(jù)量隨著建模字段增加而變化的曲線,如下圖所示:

其中覆蓋率根據(jù)歷史請求日志代入聚合表計算得出。

我們的原則是:針對OLAP查詢,聚合表的數(shù)據(jù)量應盡可能的控制在單日1億條以內(nèi),請求覆蓋率盡可能達到80%以上。

因此不難得出結(jié)論:選擇14個維度字段對聚合表建模比較理想,數(shù)據(jù)量能控制到單日8千萬條左右,且請求覆蓋率約為83%。

4.2 使用物化視圖

在分析報告歷史查詢?nèi)罩緯r,我們發(fā)現(xiàn)不同的維度字段查詢頻次有明顯的分層:

Top7維度字段幾乎出現(xiàn)在所有報告的查詢條件之中,對于如此高頻的查詢,值得做進一步的投入,使查詢效率盡可能的提升到最佳。

Doris的物化視圖能夠很好的服務于此類場景。

什么是物化視圖?

物化視圖是一種特殊的物理表,其中保存基于基表(base table)部分字段進一步上卷聚合的結(jié)果。

雖然在物理上獨立存儲,但它是對用戶透明的。為一張基表配置好物化視圖之后,不需要為其寫入和查詢做任何額外的工作:

當向基表寫入和更新數(shù)據(jù)時,集群會自動同步到物化視圖,并通過事務方式保證數(shù)據(jù)一致性。

當對基表進行查詢時,集群會自動判斷是否路由到物化視圖獲取結(jié)果。當查詢字段能被物化視圖完全覆蓋時,會優(yōu)先使用物化視圖。

因此我們的查詢路由如下圖所示:

用戶的查詢請求會盡可能的路由到聚合表物化視圖,然后是聚合表基表,最后才是明細表。

如此使用多梯度的聚合模型的配合來應對熱度分層的查詢請求,使聚合數(shù)據(jù)的效能盡可能的發(fā)揮到最大。

4.3 精確匹配取代LIKE查詢

既然物化視圖這么好用,為什么我們不是基于Doris明細表配置物化視圖,而是單獨開發(fā)聚合表呢?

是因為明細數(shù)據(jù)中的實驗組ID字段存儲和查詢方式并不合理,聚合數(shù)據(jù)并不適合通過明細數(shù)據(jù)直接上卷來得到。

3.3節(jié)已經(jīng)提到,exp_id(實驗組ID)在明細表中以逗號分隔的字符串進行存儲,查詢數(shù)據(jù)時使用LIKE方式匹配。作為AB實驗報告查詢的必查條件,這種查詢方式無疑是低效的。

我們希望的聚合方式如下圖所示:

我們需要將exp_id字段拆開,把數(shù)據(jù)打平,使用精確匹配來取代LIKE查詢,提高查詢的效率。

控制聚合表數(shù)據(jù)量

如果只做拆分打平的處理必然會導致數(shù)據(jù)量的激增,未必能達到正向優(yōu)化的效果,因此我們還需要想辦法來壓縮exp_id打平后的數(shù)據(jù)量:

聚合表選取維度字段建模的時候,除了4.1節(jié)提到的,以字段的使用頻次熱度作為依據(jù)之外,也要關注字段的取值基數(shù),進行綜合取舍。如果取值基數(shù)過高的維度字段進入聚合表,必然會對控制聚合表的數(shù)據(jù)量造成阻礙。因此,我們在保證聚合表請求覆蓋量的前提下,酌情舍棄部分高基數(shù)(取值有十萬種以上)的維度。

從業(yè)務的角度盡可能過濾無效數(shù)據(jù)(比如一個實驗組的流量為0%或者100%,業(yè)務上就沒有對照的意義,用戶也不會去查,這樣的數(shù)據(jù)就不需要進入聚合表)。

經(jīng)過這一系列步驟,最終聚合表的數(shù)據(jù)量被控制在單日約8000萬條,并沒有因為exp_id打平而膨脹。

值得一提的是,exp_id字段拆分后,除了查詢從LIKE匹配變?yōu)榫_匹配,還額外帶來了兩項收益:

字段從String類型變?yōu)镮nt類型,作為查詢條件時的比對效率變高。

能利用Doris的前綴索引布隆過濾器等能力,進一步提高查詢效率。

4.4 使用BITMAP去重代替COUNT DISTINCT

要提速實驗報告查詢,針對進組人數(shù)(去重用戶數(shù))的優(yōu)化是非常重要的一個部分。作為一個對明細數(shù)據(jù)強依賴的指標,我們?nèi)绾卧诓粊G失明細信息的前提下,實現(xiàn)像Sum,Min,Max等指標一樣高效的預聚合計算呢?

BITMAP去重計算可以很好的滿足我們的需求。

什么是BITMAP去重?

BITMAP去重簡單來說就是建立一種數(shù)據(jù)結(jié)構(gòu),表現(xiàn)形式為內(nèi)存中連續(xù)的二進制位(bit),參與去重計算的每個元素(必須為整型)都可以映射成這個數(shù)據(jù)結(jié)構(gòu)的一個bit位的下標,如下圖所示:

計算去重用戶數(shù)時,數(shù)據(jù)以bit_or的方式進行合并,以bit_count的方式得到結(jié)果。更重要的是,如此能實現(xiàn)去重用戶數(shù)的預聚合。BITMAP性能優(yōu)勢主要體現(xiàn)在兩個方面:

空間緊湊:通過一個bit位是否置位表示一個數(shù)字是否存在,能節(jié)省大量空間。以Int32為例,傳統(tǒng)的存儲空間為4個字節(jié),而在BITMAP計算時只需為其分配1/8字節(jié)(1個bit位)的空間。

計算高效:BITMAP去重計算包括對給定下標的bit置位,統(tǒng)計BITMAP的置位個數(shù),分別為O(1)和O(n)的操作,并且后者可使用CLZ,CTZ等指令高效計算。此外,BITMAP去重在Doris等MPP執(zhí)行引擎中還可以并行加速處理,每個節(jié)點各自計算本地子BITMAP,而后進行合并。

當然,以上只是一個簡化的介紹,這項技術(shù)發(fā)展至今已經(jīng)做了很多優(yōu)化實現(xiàn),比如RoaringBitmap,感興趣的同學可以看看:GitHub - RoaringBitmap/RoaringBitmap

全局字典

要實現(xiàn)BITMAP去重計算,必須保證參與計算的元素為UInt32 / UInt64,而我們的user_id為String類型,因此我們還需設計維護一個全局字典,將user_id映射為數(shù)字,從而實現(xiàn)BITMAP去重計算。

由于聚合數(shù)據(jù)目前只服務于離線查詢,我們選擇基于Hive表實現(xiàn)全局字典,其流程如下:

指標聚合

生成Doris聚合表時,將user_id作為查詢指標以BITMAP類型來存儲,其他常規(guī)查詢指標則通過COUNT / SUM / MAX / MIN等方式聚合:

如此明細表和聚合表的指標計算對應關系如下:

五、優(yōu)化效果

SQL視角

查詢請求轉(zhuǎn)換成SQL之后,在明細表和聚合表的表現(xiàn)對比如下:

常規(guī)聚合指標查詢的性能提升自不必說(速度提升50~60倍

進組人數(shù)查詢性能的提升也非常可觀(速度提升10倍左右

集群視角

SQL查詢的快進快出,使查詢占用的資源能快速釋放,對集群壓力的緩解也有正向的作用。

Doris集群BE節(jié)點CPU使用情況和磁盤IO狀況的改變效果顯著:

需要說明的是,集群狀況的改善(包括實驗報告查詢P95提升)并不全歸功于數(shù)據(jù)預聚合優(yōu)化工作,這是各方合力協(xié)作(如產(chǎn)品業(yè)務形態(tài)調(diào)整,后端查詢引擎排隊優(yōu)化,緩存調(diào)優(yōu),Doris集群調(diào)優(yōu)等)的綜合結(jié)果。

六、小技巧

由于業(yè)務查詢需求的多樣,在查詢明細表時,會出現(xiàn)一個字段既作為維度又作為指標來使用的情況。

如廣告業(yè)務表中的targetConvNum(目標轉(zhuǎn)化個數(shù))字段,此字段的取值為0和1,查詢場景如下:

--作為維度

selecttargetConvNum,count(distinct user_id)

from analysis.doris_xxx_event?

where olap_date = 20221105

and event_name='CONVERSION'

and exp_id like '%154556%'group bytargetConvNum;

--作為指標

select sum(targetConvNum)

from analysis.doris_xxx_event?

where olap_date = 20221105

and event_name='CONVERSION'and exp_id like '%154556%';

如果這個字段被選取進入聚合表,應該如何處理呢?

我們的處理方式是:

在聚合表中把這類字段建模成維度

聚合表中需要一個計數(shù)指標cnt,表示聚合表中一條數(shù)據(jù)由明細表多少條數(shù)據(jù)聚合得到

當這類字段被作為指標查詢時,可將其與cnt指標配合計算得到正確結(jié)果

明細表查詢:

select sum(targetConvNum)

from analysis.doris_xxx_event

where olap_date = 20221105

and event_name='CONVERSION'

and exp_id like '%154556%';

對應的聚合表查詢:

select sum(targetConvNum * cnt)

from agg.doris_xxx_event_agg

where olap_date = 20221105

and event_name = 'CONVERSION'

and exp_id = 154556;

七、結(jié)束語

經(jīng)過這一系列基于Doris的性能優(yōu)化和測試,A/B實驗場景查詢性能的提升超過了我們的預期。值得一提的是,Doris較高的穩(wěn)定性和完備的監(jiān)控、分析工具也為我們的優(yōu)化工作提效不少。希望本次分享可以給有需要的朋友提供一些參考。

最后,感謝SelectDB公司和Apache Doris社區(qū)對我們的鼎力支持。Apache Doris是小米集團內(nèi)部應用最為廣泛的OLAP引擎之一,目前集團內(nèi)部正在推進最新的向量化版本升級工作。未來一段時間我們將會把業(yè)務優(yōu)化工作和Doris最新的向量化版本進行適配,進一步助力業(yè)務的正向發(fā)展。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容