![]()
作者 | 作業幫大數據團隊(覃爭、孫建業、劉澤強)
本文主要分享 25 年 StarRocks 替換 Presto 的探索與實踐,包括歷史背景、選型思考、技術方案以及過程中遇到的核心問題。
歷史背景
作業幫 Presto 主要應用在即席查詢場景,基本不用于 toB 系統和例行數倉構建場景。天級查詢規模大概在 2000 ~ 5000 次,均值查詢耗時分鐘級別,整體偏慢;
Presto、Yarn、HDFS 是混布的,進程間只做內存資源限制,高峰期宿主節點 Cpu 幾乎打滿,嚴重影響即席查詢體驗,業務負反饋明顯。針對核心業務采用獨立部署方式緩解;
在 toB 系統的 OLAP 場景使用 StarRocks 多年,團隊對 StarRocks 的理解比 Presto 更深刻,并且高版本 StarRocks 存算分離已經支持 Trino Dialect;
Presto 版本較老,不支持查詢已有 Iceberg 表;
技術方案
StarRocks 采用全面向量化引擎和基于 CBO 的智能查詢規劃,在復雜的多表關聯查詢場景下性能表現很好,同時原生支持具備 Iceberg 查詢能力,社區成熟技術迭代快。同時在存算一體場景采用 StarRocks 已有很多經驗,所以采用 StarRocks 替換 Presto。為避免業務擾動且收益正向,核心是平臺層面架構適配、解決語法兼容性和性能優化、任務遷移。
整體架構
即席查詢整體架構如下圖。用戶通過數據平臺編輯 SQL 任務,提交給 QueryEngine 即席查詢服務(任務管理、語法校驗、結果脫敏、日志可讀性轉換等等),再由 QueryEngine 提交給計算網關 Teralink(權限認證、審計、分發、引擎入口收斂等),Teralink 根據具體執行引擎提交給集群。StarRocks 采用存算分離模式部署,利用 Catalog 查詢 Hive、Iceberg 數據。考慮到長期 StarRocks 和 Spark 基于 k8s 彈性,做了容器部署。為避免業務擾動最大化兼容 Presto sql 語法,詳細內容見下文。在 StarRocks 內部解析異常時也可以回退到 StarRocks Dialect Parser 起到一定補充作用。
在遷移方案時,為了保障穩定和數據準確,在 QueryEngine 這層做了防御措施。當使用 StarRocks 集群查詢失敗后回退到 Presto。在準確性方面,根據已有雙跑結果建立 StarRcoks SQL 指紋庫,指紋庫以外的查詢 diff 數據結果,數據準確完善指紋庫信息,預期之外情況人工介入解決。
![]()
雙跑方案
資源節省的角度考慮,我們沒有將 Presto 和 StarRocks 的資源打平,而是 Presto 利用現有集群,StarRocks 利用測試小集群,資源情況如下:
Presto 混布以單節點可用最大內存、cpu 內存比為 1:3.5 計算,其中一個集群大概 2500 多核(白天 cpu 有空閑)
StarRocks 資源情況 6 CN * 32 核 = 192 核
在業務低峰期針對近 N 天的查詢進行雙跑。大概步驟如下
過濾出 Presto 執行成功的 SQL,先 explain,explain 不通過的跳過,并記錄;
雙跑串行查詢(StarRocks 多次),記錄數據、耗時等信息;
分析耗時、利用 sum(hash(column)) 對比結果數據;
![]()
結果分析
兼容結果:通過 3 個多月的數據驗證 diff,遇到主要問題如下,大多數通過改造解決,少數開發成本高且使用率低的通過報錯后給替代方案解決。
性能結果:StarRocks 的整體性能符合預期,緩存以后查詢性能也有明顯的提升。
![]()
緩存加速
歷史因 Hive 數據存儲和計算 cpu 增長不成線性比例,用 Cos 替換了 HDFS,做了離線場景的存算分離。查詢遠端 cos 數據與 StarRocks data cache 數據時,性能上還是有很大差距的,Cos 內部并沒有數據格式的概念,查詢引擎很難利用 parquet 格式特殊性實現 data pruning,加上網絡請求的耗時,查詢速度會有衰減。為了盡可能提高查詢效果,我們會利用 SQL 解析獲取最近 N 天查詢過的表,監聽這些表新增分區,自動觸發查詢進行數據緩存,命中率情況如下圖。
分析 data cache 的原理,緩存文件由 CN 節點個數、Host Ip 和 Port 決定的。在 K8S 上 StarRocks CN 節點采用的是 StatefulSet 方式進行部署,雖然我們目前還沒有走彈性擴縮的邏輯,但是 StarRocks CN Pod 的重啟 / 重建也會影響 data cache 的分布。因此我們目前的部署采用的是:固定資源池 + Pod 滾動重啟 / 重建 + Pod 規格基本用滿一個節點 組合的方式,來控制 pod 不會發生漂移。后面待云上能力支持完善后, 我們會采用 Local PVC 的方式來防止 Pod 漂移,同時考慮引入 StarRocks 4.0 新增的緩存共享能力。
![]()
核心問題
平臺語法解析慢問題
問題背景:平臺側是 explain 來實現語法檢測的,Presto 基本秒級返回,StarRocks 耗時比較久,有的甚至超過 30s ~ 1min
原因分析:explain 過程包含多個階段 Parse、Analyze、Logical Plan、SQL Optimize、 生成 Plan Fragment。分析 Profile 發現耗時主要在 SQL Optimize 階段,RBO/CBO 獲取查詢源信息階段。StarRocks 現有的 explain 能力不支持跳過 SQL 優化階段
解決方案:調整 SQL 為 explain select * from( {user_original_sql} ) where 1!=1
Cancel 查詢無效
問題背景:除了 StarRocks、Presto 外還有長時運行的 Spark 任務,平臺側提供了運行中任務取消能力;
原因分析:實際上是 2 個問題
在 Teralink(基于 Kyuubi 二開)中,JdbcSQLEngine 通過調用 MySQL Statement.close() 來處理 cancel 請求。但由于 Statement.close() 需要獲取一把 Statement 內部操作鎖,而該鎖只有在 SQL 執行結束后才會釋放,導致 cancel 請求被阻塞,直到 SQL 執行完成,從而無法真正中斷正在運行的 SQL。
MySQL Statement cancel 會新創建一個 connection,而 StarRocks 對外暴露的是 LB, 默認沒有開啟會話保持,新建 connection,會路由到不同的后端 FE 上
解決方案:
對 JdbcSQLEngine 進行了調整,在 JdbcDialect 中引入 cancel 方法,并在 cancel 流程中先 cancel Statement 的執行,再進行 close,以確保 SQL 能夠被 kill
因 LB 開啟會話保持會導致 FE 請求不均、某些查詢時間比較長,超過保持時間同樣有問題。在 Teralink 這層針對 cancel 設置重試策略,檢測到相關錯誤,繼續重試 cancel,并設置重試上限;
Iceberg 表緩存導致 FE OOM
問題背景:查詢 Iceberg 表時,FE 內存變化較大,偶發 OOM 導致 pod 重啟
原因分析:查詢 Iceberg 表時大概邏輯為 1. 先檢測 metadata.json 文件更新時間判斷緩存是否過期。2. 如果過期則拉取對應 snap 文件并獲取到 m0 文件列表。3. 解析 m0 文件列表定位數據文件。 當 Iceberg 表比較大或者頻繁更新時產生很多 m0 文件,第 2 步內存 fe 內存會緩慢增加,第 3 步 fe 內存會劇烈增加,引起 FE OOM 問題
解決方案:關閉 iceberg 表元數據緩存、利用 starrocks 自身 skip manifest 文件的能力在查詢時快速進行分區過濾并定位 m0 文件;
iceberg 表 plan_mode = distributed 報錯
![]()
問題背景:starrocks 在使用分布式模式解析 iceberg 表元數據時會把虛擬的 metadataTable 當成 hive 表再去 metastore 獲取元數據而報錯
原因分析:iceberg 表生成執行計劃有兩種方式。本地模式,使用 fe 進行 metadata.json + snap.avro + m0.avro 文件解析。分布式模式,預設一個 metadata 表及其表結構并把 iceberg 表的元數據 avro 文件當成 hive 普通表的 avro 文件利用 cn 分布式處理。starrocks 采用的是自動選擇模式,根據所需掃描的 m0 文件的總大小及數量進行選擇。當選擇 distributed 模式時會穩定報錯,因為在權限驗證階段會強制從 metastore 獲取 metadata 表元數據
解決方案:修改代碼,如果檢測到表類型是 iceberg 表的虛擬元數據表,即 metadataTable,則不進行元數據獲取
multi_distinct_count 執行慢問題
問題背景:當 sql 含有多個 count_distinct 表達式,單 CN 節點內存使用極高、cpu 空閑、執行速度慢,無法多并發執行;
原因分析:當查詢包含 count_distinct 時,StarRocks 會有些內部判斷邏輯
如果數據量很大的情況下,分出多個數據流分別進行 streaming aggregate 最后 nestloop join 成單條
![]()
如果數據量小且列基數都低,重寫成 multi_disticnt_count 函數單點執行
![]()
當統計信息缺失則可能誤判,也就是在數據量很大時誤用 2,導致問題。
解決方案:由于不是所有表都具有完整統計信息,所以禁止 multi_disticnt_count 優化 set global prefer_cte_rewrite=true,放棄小查詢性能收益,保障整體查詢速度穩定
執行計劃生成耗時長
問題背景:starrocks 為生成更優的執行計劃而在 plan 階段會做更加詳細的統計信息收集,導致 plan 階段時間長,而且為保證數據準確性我們關閉了 fe 文件元數據緩存
原因分析:抽取具體 SQL 分析 trace times(見下圖)。在 hive 表統計信息缺失時,優化器會獲取全量文件列表推導統計信息,hive 表文件過多會導致在 rbo 階段速度很慢。
![]()
解決方案:增大 async_refresh_max_thread_num 到 128,以 128 線程并發獲取分區的文件列表。默認超時時間在存算分離查詢數據湖場景偏低,加大 set global new_planner_optimize_timeout=60000 緩解
limit 方式限制返回條數導致結果亂序
問題背景:為限制 sql 返回數據條數,代理層會默認在原始 sql 外層嵌套一層 limit 表達式
原因分析:增加 limit 后分析 explain,因為外層沒有排序條件而被判定內層的排序條件誤用,所以內層的 order by 被刪除導致查詢結果不符合預期。
解決方案:設置 global sql_select_limit = n,在原有執行計劃樹添加一個 TOP-N Node 解決。
![]()
中間結果落盤導致 CN core dump
問題背景:為緩解查詢內存不足問題開啟中間結果落盤,但 spill 過程中偶發 core dump
原因分析:中間結果落盤時如果數據過大會觸發限制導致 cn 進程 core dump
![]()
解決方案:修改代碼,當批數據過大則不走 lz4 壓縮,直接落盤
CN 內存不足
線上整體采用 32c * 128G 規格的機器,大概 30 多臺,數據量 PB 級,最大并發 30。偶爾會出現 StarRocks CN 節點內存過高,導致 Full GC 和 pod 被 kill 問題。內存問題總體比較復雜,從實際運行情況看并非單一原因。CN 節點整體內存占用情況如圖。 詳細原因和解決方案如下
![]()
項目收益
項目上線后,整體已運行平穩,主要有三方面的收益。
資源收益:原來 Presto 集群總共占有 4300c 左右的資源,遷移到 StarRocks 上,我們只用了 1000c 的資源。
架構收益:多個 presto 集群統一為一個 StarRocks 集群,容器部署同時為后續與 Spark 彈性擴縮提供基礎。
性能收益:P90 耗時查詢相對 Presto 縮短 2 ~ 3 倍
![]()
未來規劃
自動將即席查詢 Spark SQL 轉化為 StarRocks SQL,加快查詢速度;
白天即席查詢 StarRocks 和晚上例行 Spark 任務資源彈性;
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.