火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

導讀:

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。 重點最佳化方案

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

2。 方案一:預聚合,壓縮查詢事件量

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

雖然指標很靈活,但是大多數場景使用者進入報告頁只會檢視進組資訊,實驗結論,指標天級統計資料等,很少實時帶條件去查詢。因此,天級查詢是我們主要使用場景。天級查詢可以透過「預計算」加速。為了支援置信度的計算,「預計算」可以從人的粒度著手,即每天儲存一條人的聚合後結果,記錄下這個人在所有實驗下進組之後各指標下的累積值。這樣每天資料量與日活量相當,可以大大壓縮總體查詢量。

(1)方案詳情

總體流程圖:

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

分為如下幾個關鍵步驟: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),自帶的聚合函式無法滿足我們的要求。

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

Query

即資料如何查詢,透過對查詢引擎增加引數控制是否走預聚合邏輯,同時針對預聚合定製了查詢實現。

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

(2)資源使用限制

私有化場景使用者機器資源是非常寶貴的,夜間也有很多定時任務在執行會爭搶資源。為了保證不佔用太多資源,提交任務時會對spark引數做控制。

以如下引數為基準,對spark。dynamicAllocation。maxExecutors進行控制:

driver-memory:4g

executor-memory:2g

executor-cores:2

配置梯度表:

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

(3)效能提升表現

4億事件量,100w使用者量,查詢提升超過4倍。

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

3。 方案二:ab_log,減小join時右表的大小

(1)背景

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

(2)方案概覽

① 從實時流中過濾出曝光事件,把使用者和進組時間寫進實時clickhouse表。

② 從clickhouse實時表中構建出天粒度的離線使用者進組資訊表,每天每個使用者僅有1條進組記錄,記錄了該使用者該天最早的進組時間。

③ 查詢的時候,為了獲得使用者首次進組時間,取min(「實時表中該使用者當天的進組時間」,「離線表實驗開始到T-1天資料中該使用者進組時間」)。

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

(3)提升效果

① 透過天級進組表大大加速服務端實驗進組人群的圈選。

② 徹底解決私有化進組使用者屬性的隱患。

③ 在私有化環境可以一定程度上減少曝光事件量。在某些客戶下,可減少30%以上事件量。

4。 方案三:GroupBy查詢最佳化

(1)背景

DataTester的資料查詢和其他資料應用產品不同,DataTester在資料查詢時,所有的查詢都會針對每一個實驗版本都查一遍,而過程中中唯一的區別就在於實驗版本ID,所以和SQL中GroupBy的應用場景特別契合,透過GroupBy查詢不僅可以極大的減少查詢的數量,也可以降低多次查詢造成的重複掃表,提高查詢效率。

(2)最佳化方案

DataTester對每個實驗版本的查詢語句都是類似的,只是版本id不同。對DataTester用到的所有查詢型別和運算元做GroupBy的改造,實現細節這裡不做過多展開。

(3)提升效果

測試資料規模為日均一億,7天,3個實驗版本

查詢引擎介面響應時長(取10次平均):

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

5。 方

案四:au類指標最佳化,減少重複查詢次數

(1)背景

指標查詢引擎對DataTester的au型別運算元都做了定製,一個指標查詢會產生兩條sql,一條正常指標的查詢sql,另一條是對any_event的au的查詢,在最後結果處理的時候對兩條sql的查詢結果做了一個合併,一起返回到DataTester的科學計算模組。但是,每次開啟報告頁都必定會查進組人數,它和any_event的au是同一個值,au型別運算元查詢的時候無法複用進組人數的結果,而au查詢又可以算是最慢的查詢之一,降低了報告頁開啟的速度。

對有進組指標的運算元做了快取最佳化,減少重複查詢。

(2)最佳化方案

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

6。 方案五:非同步查詢最佳化,解決頁面超時問題

(1)背景

DataTester報告頁等一些查詢資料的介面本身確實比較耗時,需要實時計算,而大部分閘道器都有超時限制,這個問題在私有化中尤為明顯,所以對報告頁的整體互動做了最佳化改造。

(2)方案介紹

前後端互動

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

服務端架構設計

火山引擎DataTester:5個最佳化思路,構建高效能AB實驗平臺

(3)使用者體驗改進效果

① 大幅縮短請求延時,避免出現頁面請求失敗的情況

② 透過增加redis快取,同頁面的多次重新整理響應時間可以控制在100ms左右

7。 其他最佳化方案

① 業務邏輯最佳化,報告概覽核心指標顯著性和進組共用查詢結果,去除實驗版本按照核心指標顯著性的排序,14個SQL降至10個,降低28。5%⬇️

② 多維度併發控制,限制資源使用

③ 預設使用備查詢,充分利用備節點的算力

④ 靈活開關多種報告的快取,保證核心鏈路正常執行

03

總結

作為一站式A/B測試平臺,火山引擎DataTester最核心的功能之一就是指標查詢部分,它關係到產品體驗和資源佔用情況。而作為TOB領域的資料產品,DataTester能在有限的資源下發揮最極致的產品資料體驗,也是產品最為重要的競爭力之一。

本次分享了DataTester在報告頁查詢最佳化過程中的5個技術方案落地。

預聚合和ablog是從資料構建角度減少查詢資料量的角度的最佳化,groupby和au類指標的最佳化是從減少併發的角度,非同步查詢是從產品體驗角度。

查詢和資料構建密不可分,DataTester未來的產品最佳化也會按照“去肥”和“增瘦”兩個方向進行,“去肥”是最佳化科學計算模組和查詢引擎的整體架構,最佳化業務邏輯,使得報告頁查詢邏輯更加清晰和簡潔;另一方面“增瘦”就是透過合理的資料構建和資料模型最佳化加速查詢,同時定向對部分難點問題重點最佳化,比如留存、盒須快照、同期群等等。

開啟App看更多精彩內容