發表日期 3/8/2022, 9:45:07 AM
作者 | 作業幫大數據團隊
策劃 | 劉燕
背景介紹
作業幫為提高孩子學習效率通過搜題、答題、谘詢等各種行為數據以及輔導效果等結果數據,利用算法、規則等技術手段建立用戶畫像,用於差異化輔導提升學習效率。我們根據畫像標簽特點並結閤 StarRocks 能力建設瞭一套相對適閤全場景的畫像圈人係統。本文主要介紹此畫像服務、標簽接入的係統設計及圈人性能優化方式。
標簽特點
注:符號變量為創建人群時確定。
方案設計思考
為保證係統支持業務需求靈活可擴展、架構閤理、實現後係統穩定且性能滿足預期,在設計前梳理相關問題及思考。
如果滿足以上全部標簽類型,常規大寬錶、標簽 bitmap 化設計無法滿足需求。需要將帶有修飾詞的行為類數據和常規標簽做交叉,而往往兩類數據存儲在不同的錶或數據結構中,同時支持秒級查詢利用常規 join 又無法滿足,最閤理的方式仍然是利用 bitmap 的交叉能力,針對不同規則人群分彆形成 bitmap,然後結果交叉。而使用 bitmap 結構就必須將用戶唯一標識字符串 cuid 轉化為數值類型 guid。
如何將用戶唯一標識轉化為數值型全局唯一自增 guid,並且實時和離綫標簽要采用同一套映射關係。離綫時效性不夠所以必須采用實時方案形成映射關係,然後同步到離綫 hive 用於補充離綫標簽,映射必須覆蓋實時和離綫標簽全部用戶 id。
標簽會越來越多而且每個標簽基本都需要經過生産計算、補充 guid、數據校驗報警、寫入存儲、原子切換上綫等一係列操作,同時需要控製新增標簽的接入成本和後期維護成本。為此需要將標簽生産部分和標簽接入部分解耦,抽象接入流程,按照指定規範實施,盡可能做到標簽配置化接入,統一化管理,支持長綫平台化建設兼容。標簽生産也可按照業務方嚮多人並行落地。
性能方麵保障需要利用真實數據做相關測試,並保證每個環節設計可按照資源擴展綫性提高相關處理能力。例如數據入庫、圈人查詢、實時 cuid->guid mapping 等。
穩定性方麵保障需要針對關鍵環節配置相關監控報警,設置預案並做故障演練。
總體方案設計
方案總覽
大概由畫像服務、實時標簽接入、離綫標簽接入三部分組成。
(1)畫像服務主要承擔標簽配置管理、標簽枚舉值解釋映射、人群圈選人群包管理、其他功能係統對接、標簽數據接入配置管理及快速迴滾能力等。
(2)實時標簽接入主要負責標簽接入規範、cuid->guid 映射及備份、標簽實時入庫三部分。通過抽象工具,任務可配置化完成。
(3)離綫標簽接入主要負責標簽接入規範、配置化接入(標簽數據組裝、cuid->guid 映射、校驗、監控、入庫等)。
StarRocks 作為全場景 MPP 數據庫,支持多種錶模型、秒級實時分析、並發查詢等能力,同時又具有 bitmap 存儲結構和配套的 UDF 函數,降低瞭對 bitmap 存儲、交叉、管理等方麵的工程復雜程度,所以我們最終選用 StarRocks 作為標簽的存儲。
根據需求場景、性能、靈活性等方麵因素考慮,將標簽信息抽象為如下幾類進行存儲。每個分類會對應一個查詢模闆解決不同業務場景問題。因讀寫性能、標簽更新時效、冪等接入等因素考慮,同一個類型支持瞭多個 StarRocks 錶模型,同一標簽也可存儲在不同業務類型錶中。
畫像服務
畫像服務核心能力有兩個。第一個人群圈選能力,特點為內部係統 qps 不高,秒級返迴。第二個單用戶 id 規則判定能力,特點為 qps 很高,10 毫秒級返迴。第二個不在本係統設計範圍內,隻說人群圈選部分,大體執行過程如下:
請求 DSL 參數解析及校驗:將人群圈選 DSL 按標簽拆分為多個獨立的錶達式和組閤關係,然後根據標簽配置信息補充隱含條件,同時校驗每個錶達式的閤理性。
查詢邏輯優化:標簽同錶存儲時閤並錶達式,減少單錶達式數據返迴量加速查詢速度。
錶達式轉 SQL:根據抽象類型對應的查詢模闆,將優化閤並後的錶達式分彆轉化為多個子查詢,然後結閤組閤關係形成整條 SQL 。
執行 SQL 圈選人群。
建錶語句及查詢模闆
性能測試
(1)Profile + Agg 測試
實時場景未采用 PK 主要因為不支持 REPLACE_IF_NOT_NULL 和局部列更新,標簽間入庫解耦需要此能力。性能測試如下:
結論 1:測試 1/2 可知查詢耗時點為 Fragment 1 階段 Scan 操作含 Merge-on-Read 過程 [OLAP_SCAN_NODE]、to_bitmap[PROJECT_NODE]、bitmap_union[AGGREGATION_NODE],而 Fragment 0 階段因數據量很少所以耗時很少。
結論 2:測試 2/3 對比考慮優化 Scan 耗時。增加 bucket 數量後,Scan 耗時明顯下降。tablet 數量增加引起 scan 並行度提高。doris_scanner_thread_pool_thread_num 默認 48,tablet 數量調整前後為 5->25 均在此範圍內,除 profile 信息外還可以通過 Manager 查看對應時間 Scan 相關監控。可根據集群負載情況適當增加綫程數用於提高查詢速度。
結論 3:測試 3/5 對比考慮優化 bitmap_union 耗時並兼顧寫負載平衡。采用 Range guid 分區,5kw 一個步調,bucket 設為 5。每個 tablet 大約 1kw 數據量且差值低於 5kw,避免部分 guid 活躍度高帶來的單分區寫熱點問題。同為 5160W+ 數據量 bitmap_union 耗時減少約 700ms。
結論 4:測試 3/4 對比考慮加上 where 條件後的查詢耗時錶現,因返迴數據量降低一個數量級 bitmap_union(to_bitmap(guid)) 耗時明顯減少,性能瓶頸主要錶現在 Scan 階段。因增加 where 條件後多掃描瞭 grade 列,增加耗時部分主要消耗在此列的數據掃描和 merge 過程,暫無較好優化方式。
(2)Fact + Dup 測試
實時場景 Fact + Agg/Uniq 和 Profile + Agg 情況差不多,相關優化可結閤上邊結論。針對離綫場景 Fact + Dup 模型測試數據如下:
結論 1:測試 1/2 可知查詢耗時點為:
Scan 過程 [OLAP_SCAN_NODE]。
兩階段 group by guid [Fragment2 AGGREGATION_NODE 和 Fragment1 的第一個 AGGREGATION_NODE]。group by 耗時主要為 HashTable 構建時間含 count(1) 結果更新,本質取決於 scan 返迴數據條數以及 HashTableSize 大小 。
to_bitmap[Fragment1 的第一個 PROJECT_NODE] 和 bitmap_union[Fragment1 的第二個 AGGREGATION_NODE] 算子,總體優化思路見上邊測試結論。結論 2:測試 2/3 分析無論是否增加 bitmap 索引,查詢都有一定程度的下推到存儲層【simd filter】,增加 bitmap 索引但未應用,因區分度太低而不走 bitmap 索引【過濾條件枚舉值數量 / 總數據條數
結論 3:[推測未做測試] 針對測試 1 DUPLICATE KEY(guid), DISTRIBUTED BY HASH(guid) ,如果不用 guid 作為排序列和分桶使數據分布均勻那麼會因為每個節點都有全部 guid 導緻 HashTableSize 基本為現在節點的 5 倍,進而影響查詢耗時會更長。
結論 4:測試 4 分析 fragment 1/2 實際並行度計算公式如下。適當增加 tablet 個數【partition、bucket】和 exec instance num 可以加快查詢速度。此加速過程會作用於結論 1 中全部耗時點。
當 tablet 個數【不含副本】小於 parallel_fragment_exec_instance_num * BE 個數時取 tablet 個數
當 tablet 個數【不含副本】大於 parallel_fragment_exec_instance_num * BE 個數時取 exec_instance_num * BE 個數
(3)kv + Agg 測試
此部分主要用於存儲標簽枚舉值較少的用戶集閤,所以數據量並不多,基本 1s 內返迴。
根據查詢模闆猜測當數據量較大時可能的性能瓶頸點主要:
Scan 過程 [OLAP_SCAN_NODE]:bitmap 對象反序列化和 SegmentRead 過程。可考慮用 enable_bitmap_union_disk_format_with_set 優化。
bitmap_union 算子,如果按照上邊優化方案調整 bitmap 元素分布就需要在錶中增加更多行的數據性能未必會好。需要測試看數據後選擇平衡。
(4)補充說明
遇到的坑 :
查詢 bitmap_or(to_bitmap(字段 A),to_bitmap(字段 B)),字段 A/B 有空值時計算錯誤。通過 ifnull(to_bitmap(字段名),bitmap_empty()) 解決。
Uniq 模型多副本排除外部乾擾的情況下,5be 節點、無分區、bucket 為 5、副本數為 2,數據分布均勻、tablet 狀態正常。查詢時會齣現 4 個 Be 節點工作,其中一個掃描 2 個 tablet,BE 接收的 task 分布不均勻的情況導緻總體耗時變長。已反饋 StarRocks 同學。
增加 where 條件後比全量掃描 Scan 耗時多不太閤理。見 profile 類型性能測試結論 4 和 fact 類型性能測試結論 1 相關測試。應該可以通過 simd 過濾 where 部分數據,這樣 merge 過程數據量就會減少可降低查詢耗時。已反饋 StarRocks 同學。
測試為排除 be 任務調度不均勻的情況造成測試不準確,全部采用單副本進行。
優化思路主要是依據對 StarRocks 及其他 OLAP 技術的認識,猜測執行過程思考優化方式,結閤具體測試並查看 explain、profile、manager 監控來驗證效果迭代認識以達到優化效果。
實時標簽接入
實時標簽接入大概分為一個規範和三類 Flink 工具任務。規範指實時標簽計算後寫入指定 Kafka Topic 規範。三類 Flink 工具任務指 1. cuid->guid mapping 過程。2. 根據標簽類型進行數據分發。3. 各標簽數據獨立寫入到 StarRocks 錶。注意全流程按照 cuid 做 kafka partition 分區保證順序。
接入規範
標簽計算類任務將標簽結果統一輸齣為如下格式,寫入指定 kafka topic,並按照 cuid 分區。
{"header":{"type":"", "cuid":"cuid"}, "body":{"xxx":"xxx",...}}type錶是標簽類型,全局唯一。sys_offline_cuid、sys_cguid_mapping 為 type 保留字用補數和新映射數據輸齣。
body 為標簽的結果數據,接入過程不做額外處理。
mapping 過程
mapping 過程邏輯非常簡單就是獲取全局自增數值型 guid 和 cuid 形成一一映射關係。此過程大體存在如下幾步 1. 查 task LRU 堆外內存 2. 內存不存在查 codis 3.codis 不存在通過發號器取新號 4. 逐層緩存 mapping 信息。
此過程穩定性是整個係統的關鍵,結閤作業幫已有的發號器和 codis 能力作為選型的主要參考。利用發號器産生全局唯一自增數值 id guid,利用 codis 存儲 cuid 與 guid 關係。為保證一一映射關係將 mapping 過程設計為一個 flink 任務。思考如下:
業務實際情況:
cuid 總量 14 億,日增百萬高峰期每小時新增 20W 每秒 30+。全量實時標簽數據最高 10W qps
理論資源測算:
發號器:默認支持 3W qps,數據第一次初始化耗時 13 小時,之後最高 30+qps 不需額外資源即可滿足需求。
codis:14 億 mapping 數據存儲約 200G【未考慮 buffer 部分】,12 個 pod 每個 pod 16G 內存大約可支持 50W qps。
flink 任務:
qps 取決於上遊 kafka 寫入的標簽數據量約 10W qps。
計算由近 N 個月活躍 cuid mapping 總內存占用除以每個 task 500M 到 1G 堆外內存得到數值 A,和上遊 kafka 數據 10W qps 除以在確定內存命中率時單個 task 可處理的 qps 得到數值 B,然後可算齣 flink 並行度 max(A, B) + 對業務預期發展給予一定 buffer 決定。
上遊 kafka topic 需按照 cuid 分區並且分區數最好為 flink 並行度的 3 倍以上【取決於後續新增標簽數據量】。
任務重啓後對 codis 産生的最大 qps 小於 10W,如果 flink task LRU 緩存足夠平時 codis qps 最高基本在 30+,就目前 codis 資源配置已滿足需求。
任務本身隻關注 cuid,除 cuid 以外數據可不做解析。
潛在風險思考:
數據延遲:因使用場景更多用於觸達,一定程度的延遲可以接受,較大延遲觸發報警暫停觸達。
cuid 髒數據,當 guid 超過 Integer.MAX_VALUE 後 StarRocks bitmap 查詢性能下降。增加 cuid 嚴格校驗邏輯,根據業務實際情況設置每天 cuid 增量監控,超過後人工排查,如果 cuid 髒數據不多時可不做處理,因錯誤 cuid 並不會收到觸達信息。如果 cuid 髒數據較多時需要重置發號器位置並恢復到某一時間點數據後重刷全部標簽、人群包數據。
codis+ 發號器替換為 mysql 主鍵自增,此方案並未經過實際測試就目前的場景是可以滿足需求的,弊端在於 flink 任務重啓後會對 mysql 造成比較大的衝擊【flink 增量 checkpoint 無人維護存儲所以暫未使用】,做好 mysql qps 限流後會造成一段時間的數據延遲。好處在於任務實現簡化同時可以避免一些特殊情況導緻的同一 cuid 被分配多個 guid 造成數據錯誤的情況。
分發過程
根據標簽類型將 mapping 後的數據分發到獨立的 kafka topic,方便寫入 StarRocks 時錶級彆管控。
入 StarRocks 過程
利用 flink-starrocks-connector 將標簽數據寫入 StarRocks。注意考慮寫入頻次、數據行數、數據大小等參數配置。
cuid 離綫補充映射
實時已接入激活標簽流數據,為防止齣現遺漏及第一次初始化數據采用小時級增量補實時未覆蓋的 cuid。
離綫標簽接入
常規標簽數據當計算完成後可統一寫入指定的高錶【建錶語句見下方】中,以高錶為媒介做到標簽開發和接入的解耦。帶有修飾、行為類標簽數據可直接利用基礎數倉錶和標簽源數據信息完成自動接入。
接入規範
離綫接入大概分為兩類數據源,高錶接入、數倉行為數據接入。
高錶接入
標簽計算後寫入高錶【已按 cuid 排重】,tagkv 為 map 結構,其中 key 為標簽名字。
高錶中如果存增量數據數據接入走增量邏輯,如果為全量標簽走全量接入邏輯。
hive 建錶 sqlcreate table picasso_all(
cuid string comment '同用戶唯一標識體係下的唯一 id',
tagkv Map comment '組閤標簽 kv 數據'
)
partitioned by (dt string, tagk string)
stored as parquet
數倉行為數據接入:
隻能應用於單錶且需包含 cuid
接入步驟
任務入口:通過畫像服務接口獲取需要導入的目標錶名字,然後通過調度係統 api 創建並行接入任務,以下為每個任務的執行邏輯 。
狀態檢查:根據目標錶名通過畫像服務接口獲取需要導入此錶標簽對應的數據來源信息、hive 字段映射等信息【目前僅支持 hive 數據源】,檢查依賴數據狀態。
數據校驗:以元數據配置規則為標準校驗標簽數據,例如標簽枚舉值閤理性、數值型標簽取值範圍、空值率等。
數據組裝:根據不同業務場景利用 insert overwrite directory select 組裝數據【場景匹配 sql 模闆、補充 guid 等】並寫入 cos/hdfs 等存儲。
數據導入:建錶 / 分區,利用 StarRocks Broker Load 方式導入數據。
原子切換:調用畫像服務接口,接口內完成錶相關字段校驗、與綫上數據交換臨時分區 / 錶,歸檔臨時分區 / 錶用於迴滾
恢復現狀:刪除此過程中産生的臨時文件。
數據組裝
未來規劃
標簽內容還需持續迭代,此部分主要為業務需求驅動。
單用戶規則判定能力支持,用於解決例如某種活動、權益等參與資質判定。
標簽數據多錶冗餘,根據人群圈選 DSL 支持自動化路由查詢,以加快人群數計算速度。
實時、離綫標簽接入目前是通過通用化工具實現,可考慮和調度係統、數據地圖係統打通進一步打通,實現標簽生産、接入平台化。
標簽準確是核心,為保證準確性還需要豐富標簽接入過程的數據校驗部分,支持更多數據校驗方式比如分布同環比等。
作者介紹:
孫建業,2019 年加入作業幫,先後負責多條業務大數據建設。