導讀:
DataTester是由火山引擎推出的A/B測試平臺,覆蓋推薦、廣告、搜尋、UI、產品功能等業務應用場景,提供從A/B實驗設計、實驗建立、指標計算、統計分析到最終評估上線等貫穿整個A/B實驗生命週期的服務。DataTester經過了位元組跳動業務的多年打磨,在位元組內部已累計完成150萬次A/B實驗,在外部也應用到了多個行業領域。
指標查詢的產品高效能是DataTester的一大優勢。作為產品最複雜的功能模組之一,DataTester的指標查詢能夠在有限資源的前提下,發揮出最極致的A/B實驗資料查詢體驗,而在這背後是多次的技術方案的打磨與迭代。
本文將分享DataTester在查詢效能提升過程中的5個最佳化思路。
01
現狀及問題
1。 挑戰 1:版本管理
實驗指標報告頁是DataTester系統最核心的功能之一,報告頁的使用體驗直接決定了DataTester作為資料增長和實驗評估引擎在業界的競爭力。該功能具有以下特點:
① 牽連繫統多、鏈路長
:報告頁涉及到控制檯(Console)、科學計算模組、查詢引擎、OLAP儲存引擎。整個鏈路包括了:DSL到sql轉化、後端查詢結果快取處理、查詢結果的加工計算、前端查詢介面的組裝和資料渲染。
②
實現複雜:實驗指標有多種運算元,在查詢引擎側中都有一套定製SQL,透過DSL將運算元轉換成SQL。這是DataTester中
最複雜的功能模組之一
。
02
最佳化思路
從一條SQL說起。
舉一個例子,在DataTester中一次AB測試的查詢分三部分邏輯。
①實時掃描事件表,做過濾
② 根據使用者首次進組時間過濾出使用者
③ 做聚合運算
需要查詢詳細的SQL程式碼,也可以點選展開檢視詳情。
printf(“hello world!”);SELECT event_date,
count(DISTINCT uc1) AS uv,
sum(value) AS sum_value,
sum(pow(value, 2)) AS sum_value_Square
FROM
(SELECT uc1,
event_date,
count(s) AS value
FROM
(SELECT hash_uid AS uc1,
TIME,
server_time,
event,
event_date,
TIME AS s
FROM rangers。tob_apps_all et
WHERE tea_app_id = 249532
AND ((event = ‘purchase’))
AND (event_date >= ‘2021-05-10’
AND event_date
AND multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME) >= 1620576000
AND multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME)
AND (event in (‘rangers_push_send’,
‘rangers_push_workflow’)
OR ifNull(string_params{‘$inactive’},‘null’)!=‘true’) ) et GLOBAL ANY
INNER JOIN
(SELECT min(multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME)) AS first_time,
hash_uid AS uc2
FROM rangers。tob_apps_all et
WHERE tea_app_id = 249532
AND arraySetCheck(ab_version, (29282))
AND event_date >= ‘2021-05-10’
AND event_date
AND multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME) >= 1620651351
AND multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME)
AND (event in (‘rangers_push_send’,
‘rangers_push_workflow’)
OR ifNull(string_params{‘$inactive’},‘null’)!=‘true’)
GROUP BY uc2) tab ON et。uc1=tab。uc2
WHERE multiIf(server_time 2000000000, toUInt32(TIME / 1000), TIME)>=first_time
AND first_time>0
GROUP BY uc1,
event_date)
GROUP BY event_date
DataTester底層OLAP引擎採用的是clickhouse,根據clickhouse引擎的特點,主要有兩個最佳化方向:
①減少clickhouse的join
,因為clickhouse最擅長的是單表查詢和多維度分析,如果做一些輕量級聚合把結果做到單表上,效能可以極大提升。也就是把join提前到資料構建階段,構建好的資料就是join好的資料。
② 需要join的場景,則透過減小右表大小來加速查詢
。因為join的時候會把右表拉到本地構建hash表,所以必然會佔用大量記憶體,影響效能。
1。 重點最佳化方案
2。 方案一:預聚合,壓縮查詢事件量
雖然指標很靈活,但是大多數場景使用者進入報告頁只會檢視進組資訊,實驗結論,指標天級統計資料等,很少實時帶條件去查詢。因此,天級查詢是我們主要使用場景。天級查詢可以透過「預計算」加速。為了支援置信度的計算,「預計算」可以從人的粒度著手,即每天儲存一條人的聚合後結果,記錄下這個人在所有實驗下進組之後各指標下的累積值。這樣每天資料量與日活量相當,可以大大壓縮總體查詢量。
(1)方案詳情
總體流程圖:
分為如下幾個關鍵步驟:Dump、Parse、Build、Query
Dump
即把事件dump到離線儲存中。私有化採用flume來實現:
a。 自定義timestamp interceptor防止資料漂移
b。 使用file channel檔案緩衝保證資料不丟失
Parse
從指標DSL中解析出聚合欄位、聚合型別,事件名、過濾條件指標四要素,再根據這些資訊生成md5作為clickhouse儲存的key。考慮到不同指標配置可能會配置相同的聚合欄位、聚合型別,事件名、過濾條件,生成md5的目的是保證唯一防止多次聚合。聚合型別包括count,sum,max,min,latest,distinct(暫不支援),任何運算元都可以用這幾個基礎聚合結果計算出來。如avg可以透過sum/count來計算。
Build
離線構建最核心的部分在於自定義聚合函式(UDAF),自帶的聚合函式無法滿足我們的要求。
Query
即資料如何查詢,透過對查詢引擎增加引數控制是否走預聚合邏輯,同時針對預聚合定製了查詢實現。
(2)資源使用限制
私有化場景使用者機器資源是非常寶貴的,夜間也有很多定時任務在執行會爭搶資源。為了保證不佔用太多資源,提交任務時會對spark引數做控制。
以如下引數為基準,對spark。dynamicAllocation。maxExecutors進行控制:
driver-memory:4g
executor-memory:2g
executor-cores:2
配置梯度表:
(3)效能提升表現
4億事件量,100w使用者量,查詢提升超過4倍。
3。 方案二:ab_log,減小join時右表的大小
(1)背景
(2)方案概覽
① 從實時流中過濾出曝光事件,把使用者和進組時間寫進實時clickhouse表。
② 從clickhouse實時表中構建出天粒度的離線使用者進組資訊表,每天每個使用者僅有1條進組記錄,記錄了該使用者該天最早的進組時間。
③ 查詢的時候,為了獲得使用者首次進組時間,取min(「實時表中該使用者當天的進組時間」,「離線表實驗開始到T-1天資料中該使用者進組時間」)。
(3)提升效果
① 透過天級進組表大大加速服務端實驗進組人群的圈選。
② 徹底解決私有化進組使用者屬性的隱患。
③ 在私有化環境可以一定程度上減少曝光事件量。在某些客戶下,可減少30%以上事件量。
4。 方案三:GroupBy查詢最佳化
(1)背景
DataTester的資料查詢和其他資料應用產品不同,DataTester在資料查詢時,所有的查詢都會針對每一個實驗版本都查一遍,而過程中中唯一的區別就在於實驗版本ID,所以和SQL中GroupBy的應用場景特別契合,透過GroupBy查詢不僅可以極大的減少查詢的數量,也可以降低多次查詢造成的重複掃表,提高查詢效率。
(2)最佳化方案
DataTester對每個實驗版本的查詢語句都是類似的,只是版本id不同。對DataTester用到的所有查詢型別和運算元做GroupBy的改造,實現細節這裡不做過多展開。
(3)提升效果
測試資料規模為日均一億,7天,3個實驗版本
查詢引擎介面響應時長(取10次平均):
5。 方
案四:au類指標最佳化,減少重複查詢次數
(1)背景
指標查詢引擎對DataTester的au型別運算元都做了定製,一個指標查詢會產生兩條sql,一條正常指標的查詢sql,另一條是對any_event的au的查詢,在最後結果處理的時候對兩條sql的查詢結果做了一個合併,一起返回到DataTester的科學計算模組。但是,每次開啟報告頁都必定會查進組人數,它和any_event的au是同一個值,au型別運算元查詢的時候無法複用進組人數的結果,而au查詢又可以算是最慢的查詢之一,降低了報告頁開啟的速度。
對有進組指標的運算元做了快取最佳化,減少重複查詢。
(2)最佳化方案
6。 方案五:非同步查詢最佳化,解決頁面超時問題
(1)背景
DataTester報告頁等一些查詢資料的介面本身確實比較耗時,需要實時計算,而大部分閘道器都有超時限制,這個問題在私有化中尤為明顯,所以對報告頁的整體互動做了最佳化改造。
(2)方案介紹
前後端互動
服務端架構設計
(3)使用者體驗改進效果
① 大幅縮短請求延時,避免出現頁面請求失敗的情況
② 透過增加redis快取,同頁面的多次重新整理響應時間可以控制在100ms左右
7。 其他最佳化方案
① 業務邏輯最佳化,報告概覽核心指標顯著性和進組共用查詢結果,去除實驗版本按照核心指標顯著性的排序,14個SQL降至10個,降低28。5%⬇️
② 多維度併發控制,限制資源使用
③ 預設使用備查詢,充分利用備節點的算力
④ 靈活開關多種報告的快取,保證核心鏈路正常執行
03
總結
作為一站式A/B測試平臺,火山引擎DataTester最核心的功能之一就是指標查詢部分,它關係到產品體驗和資源佔用情況。而作為TOB領域的資料產品,DataTester能在有限的資源下發揮最極致的產品資料體驗,也是產品最為重要的競爭力之一。
本次分享了DataTester在報告頁查詢最佳化過程中的5個技術方案落地。
預聚合和ablog是從資料構建角度減少查詢資料量的角度的最佳化,groupby和au類指標的最佳化是從減少併發的角度,非同步查詢是從產品體驗角度。
查詢和資料構建密不可分,DataTester未來的產品最佳化也會按照“去肥”和“增瘦”兩個方向進行,“去肥”是最佳化科學計算模組和查詢引擎的整體架構,最佳化業務邏輯,使得報告頁查詢邏輯更加清晰和簡潔;另一方面“增瘦”就是透過合理的資料構建和資料模型最佳化加速查詢,同時定向對部分難點問題重點最佳化,比如留存、盒須快照、同期群等等。
開啟App看更多精彩內容