作為一款面向 ToB 市場的產品——火山引擎 A/B 測試(DataTester)為了滿足客戶對數據安全、合規(guī)問題等需求,探索私有化部署是產品無法繞開的一條路。
在面向 ToB 客戶私有化的實際落地中,火山引擎 A/B 測試(DataTester)也遇到了字節(jié)內部服務和企業(yè) SaaS 服務都不容易遇到的問題。在解決這些問題的落地實踐中,火山引擎 A/B 測試團隊沉淀了一些流程管理、性能優(yōu)化等方面的經驗。
本文主要分享火山引擎 A/B 測試當前的私有化架構,遇到的主要問題以及從業(yè)務角度出發(fā)的解決思路。
火山引擎 A/B 測試私有化架構
架構圖
整套系統(tǒng)采用 Ansible+Bash 的方式構建,為了適應私有化小集群部署,既允許各實例對等部署,復用資源,實現最小三節(jié)點交付的目標,,又可以做在線、離線資源隔離提高集群穩(wěn)定性。集群內可以劃分為三部分:
業(yè)務服務: 主要是直接向用戶提供界面或者功能服務的, 例如實驗管理、實驗報告、OpenAPI、數據接入等。
基礎服務: 不直接面向用戶,為上層服務的運行提供支撐,例如支持實驗報告的計算引擎、為指標創(chuàng)建提供元信息的元信息服務;基礎服務同時還會充當一層對基礎設施的適配,用來屏蔽基礎設施在 SaaS 和私有化上的差異, 例如 SaaS 采用的實時+離線的 Lambda 架構, 私有化為了減少資源開銷,適應中小集群部署只保留實時部分, 計算引擎服務向上層屏蔽了這一差異。
基礎設施: 內部團隊提供統(tǒng)一私有化基礎設施底座 minibase,采用宿主機和 k8s 結合的部署方式,由 minibase 適配底層操作系統(tǒng)和硬件, 上層業(yè)務直接對接 minibase。
私有化帶來的挑戰(zhàn)
挑戰(zhàn) 1:版本管理
傳統(tǒng) SaaS 服務只需要部署維護一套產品供全部客戶使用,因此產品只需要針對單個或幾個服務更新,快速上線一個版本特性,而不需要考慮從零開始搭建一套產品。SaaS 服務的版本發(fā)布周期往往以周為單位,保持每周 1-2 個版本更新頻率。
但是,在私有化交付中,我們需要確定一個基線版本并且綁定每個服務的小版本號以確保相同版本下每套環(huán)境中的交付物等價,以減輕后續(xù)升級運維成本。通常,基線版本的發(fā)布周期往往以雙月為單位。
版本發(fā)布周期
由于私有化和 SaaS 服務在架構、實現、基礎底座上均存在不同,上述的發(fā)布節(jié)奏會帶來一個明顯的問題:
- 團隊要投入大量的開發(fā)和測試人力集中在發(fā)版周期內做歷史 Feature 的私有化適配、私有化特性的開發(fā)、版本發(fā)布的集成測試,擠占其他需求的人力排期。
- 為了將周期內集中完成的工作分散到 Feature 開發(fā)階段,重新規(guī)范了分支使用邏輯、完善私有化流水線和上線流程,讓研發(fā)和測試的介入時間前移。
解法:
1、分支邏輯
分支管理
SaaS 和私有化均基于 master 分支發(fā)布,非私有化版本周期內不特別區(qū)分 SaaS 和私有化。
私有化發(fā)布周期內單獨創(chuàng)建對應版本的私有化分支,發(fā)布完成后向 master 分支合并。這樣保證了 master 分支在任何情況下都應當能同時在 SaaS 環(huán)境和私有化環(huán)境中正常工作。
2、發(fā)布流水線
功能上線流程
發(fā)布流水線
內部搭建一套私有化預發(fā)布環(huán)境,建設了一套流水線,對 master 分支的 mr 會觸發(fā)流水線同時在 SaaS 預發(fā)布環(huán)境和私有化預發(fā)布環(huán)境更新最新 master 分支代碼,并執(zhí)行自動化回歸和人工回歸測試。這樣做的好處在于:
- 推動了具體 Feature 的研發(fā)從技術方案設計層面考慮不同環(huán)境的 Diff 問題,減少了后期返工的成本
- 測試同學的工作化整為零,避免短時間內的密集測試
- 減少研發(fā)和測試同學的上下文切換成本,SaaS 和私有化都在 Feature 開發(fā)周期內完成
挑戰(zhàn) 2:性能優(yōu)化
火山引擎 A/B 測試工具的報告計算是基于 ClickHouse 實現的實時分析。SaaS 采用多租戶共用多個大集群的架構,資源彈性大,可以合理地復用不同租戶之間的計算資源。
私有化則大部分為小規(guī)模、獨立集群,不同客戶同時運行的實驗個數從幾個到幾百個不等,報告觀測時間和用戶習慣、公司作息相關,有明顯的峰谷現象。因此實驗報告產出延遲、實時分析慢等現象在私有化上更加容易暴露。
解法:
實驗報告體系
首先,介紹下火山引擎 A/B 測試產品的實驗報告體系。以下圖的實驗報告為例:
從上往下看產出一個實驗報告必要的輸入包含:
- 分析的日期區(qū)間及過濾條件
- 選擇合適的指標來評估實驗帶來的收益
- 實驗版本和對照版本
- 報告類型, 例如:做多天累計分析、單天的趨勢分析等
指標如何定義呢?
組成指標的核心要素包括:
1、由用戶行為產生的事件及屬性
2、預置的算子
3、四則運算符
即對于一個用戶的某幾個行為按照算子的規(guī)則計算 value 并使用四則運算組合成一個指標。
由此,我們可以大概想象出一個常規(guī)的 A/B 實驗報告查詢是通過實驗命中情況圈出實驗組或對照組的人群,分析這類群體中在實驗周期內的指標值。
由于 A/B 特有的置信水平計算需求,統(tǒng)計結果中需要體現方差等其他特殊統(tǒng)計值,所有聚合類計算如:求和、PV 數均需要聚合到人粒度計算。
模型優(yōu)化
如何區(qū)分用戶命中哪一組呢?
集成 SDK 調用 A/B 分流方法的同時會上報一條實驗曝光事件記錄用戶的進組信息,后續(xù)指標計算認為發(fā)生在進組之后的事件受到了實驗版本的影響。舉個例子:
進入實驗版本 1 的事件 A 的 PV 數是 2,UV 數是 1,轉化為查詢模型是:
上述模型雖然最符合直覺,但是存在較多的資源浪費:
- 曝光事件和普通事件存儲在一張事件表中量級大
- 曝光事件需要搜索第一條記錄,掃描的分區(qū)數會隨著實驗時間的增加而增加
- 曝光事件可能反復上報,計算口徑中僅僅第一條曝光為有效事件
針對上述問題對計算模型做出一些優(yōu)化,把曝光事件轉化為屬性記錄在用戶表中,新的模型變化為:
這么做帶來的優(yōu)點是:
- 用戶表不存在時間的概念,數據增長=新用戶增速,規(guī)??煽?/li>
- 用戶表本身會作為維度表在原模型中引入,這類情況下減少一次 join 運算 模型優(yōu)化后經測試 14 天以上實驗指標多天累計報告查詢時長減少 50%以上,且隨實驗時長增加提升。
預聚合
私有化部署實施前會做前期的資源預估,現階段的資源預估選擇了“日活用戶”和“日事件量”作為主要輸入參數。這里暫時沒有加入同時運行的實驗數量是因為:
- 一是,我們希望簡化資源計算的模型。
- 二是,同時運行的實驗數量在大多數情況下無法提前預知。
但是該公式會引入一個問題:相同資源的集群在承載不同數量級的實驗時計算量相差較大。實驗數量少的場景下,當下數據處理架構輕量化,計算邏輯后置到查詢側,,指標計算按需使用,大大減輕了數據流任務的壓力。
但是假設集群中同時運行 100 個實驗,平均每個實驗關注 3 個指標加上實驗的進組人數統(tǒng)計,在當前查詢模型下每天至少掃描事件表 100*(3+1)次,如果再疊加使用自定義過濾模板等預計算條件,這個計算量會被成倍放大,直到導致查詢任務堆積數據產出延遲。
重新觀察實驗報告核心元素以及指標構成能發(fā)現:
- 指標、報告類型、實驗版本是可枚舉且預先知曉的
- 實驗命中和人綁定,版本對比先劃分出進入對照組和實驗組的人,然后做指標比較
- 基于假設檢驗的置信水平計算需要按人粒度計算方差
- 現有的指標算子均可以先按人粒度計算(按....去重除外)
是否能夠通過一次全量數據的掃描計算出人粒度的所有指標和實驗版本?
答案是可以的:掃描當天的事件數據,根據實驗、指標配置計算一張人粒度的指標表 user_agg。
通過 user_agg 表可以計算出指標計算需要的 UV 數、指標的統(tǒng)計值、指標的方差。如果對 user_agg 表的能力做進一步拓展,幾乎可以代替原始表完成實驗報告中 80%以上的指標計算,同時也很好地支持了天級時間選擇切換、用戶屬性標簽過濾等。
修改后的指標計算模型
通過經驗數據,一個用戶平均每天產生的事件量在 100-500 條不等,聚合模型通過少數幾次對當天數據的全表掃描得到一張 1/100-1/500 大小的中間表,后續(xù)的指標計算、用戶維度過濾均可以使用聚合表代替原始表參與運算。當然考慮到聚合本身的資源開銷,收益會隨著運行實驗數增加而提高,而實驗數量過少時可能會造成資源浪費,是否啟用需要在兩者之間需求平衡點。
挑戰(zhàn) 3:穩(wěn)定性
私有化服務的運維通道復雜、運維壓力大,因此對服務的可用性要求更加嚴格。A/B 測試穩(wěn)定性要求最高的部分是分流服務,直接決定了線上用戶的版本命中情況。
分流服務本身面向故障設計, 采用降級的策略避免調用鏈路上的失敗影響全部實驗結果,犧牲一部分實時性使用多級緩存保障單一基礎設施離線的極端情況下分流結果依然穩(wěn)定。
分流服務總體架構
我們將分流服務作為一個整體,一共使用了 3 級存儲,分別是服務內存、Redis 緩存、關系型數據庫。實驗變動落庫的同時,將變動消息寫入消息隊列,分流服務消費消息隊列修改內存和 Redis 緩存中的實驗配置,保證多節(jié)點之間的一致性和實時性。同時分流服務開啟一個額外協程定期全量更新實驗配置數據作為兜底策略,防止因為消息隊列故障導致的配置不更新;將 Redis 視作 Mysql 的備組件,任意失效其中之一,這樣分流服務即使重啟依然可以恢復最新版本的分流配置,保障客戶側分流結果的穩(wěn)定。
總結
火山引擎 A/B 測試(DataTester)脫胎于字節(jié)跳動內部工具,集成了字節(jié)內部豐富的業(yè)務場景中的 A/B 測實驗經驗;同時它又立足于 B 端市場,不斷通過 ToB 市場的實踐經驗沉淀打磨產品來更好的為內外部客戶創(chuàng)造價值。
本文是火山引擎 A/B 測試(DataTester)團隊在當前面向 ToB 客戶的私有化實踐中的實踐分享,文中所遇到的私有化問題的破解過程也是這一產品不斷打磨成熟,從 0-1 階段走向 1-N 階段的過程。