在寫這篇文章之前,Java 25正式發佈,其中 JEP-508 Vector API 迎來了第10次孵化,旨在提供一種向量計算的接口,從而獲得比等效標量計算更高的性能。傳統的基於Java虛擬機(JVM)的執行引擎在處理大規模數據時逐漸顯露出性能瓶頸 (標量計算) ,特別是在 CPU 密集型任務和內存管理方面。近年來,眾多大數據計算引擎開始轉向原生(Native)執行模型,採用 C++ 等語言實現向量化執行,以提升性能和適應現代硬件特性。 Databricks 團隊於 2022 年在 SIGMOD 會議上發表的論文《Photon: A Fast Query Engine for Lakehouse Systems》中表明向量化查詢引擎 Photon 有數量級的性能提升(3~10倍的性能提升),並在 100TB TPC-DS 基準測試中創下新的經審計性能記錄。
本文將從 JVM 在處理大規模數據場景下的侷限性説起,探討 Photon 在此基礎上的設計選擇,並分析目前業界的基於 Gluten + Velox 和 Native 向量化引擎的兩種方式。
一. JVM的侷限性
在該論文中,Databricks 團隊指出,放棄現有基於 JVM 的引擎,是基於觀察到當前引擎的工作負載正變得受限於 CPU,改進現有引擎的性能變得越來越困難。幾個因素導致了這一點。首先,本地NVMe SSD 緩存和自動優化 shuffle 等低級優化顯著減少了 I/O 延遲;其次,Delta Lake 支持的數據聚類等技術通過文件修剪更積極地跳過不需要的數據,進一步減少 I/O 等待時間。最後,湖倉一體引入了需要對非規範化數據、大型字符串和非結構化嵌套數據類型進行繁重處理的新工作負載,這進一步加劇了內存性能的壓力。 另一個原因是 JVM 內部即時編譯器的限制(例如方法大小限制)導致性能急劇下降。此外,本地代碼的性能通常比 JVM 引擎更容易解釋,因為內存管理和 SIMD 等特性可被明確控制。
二. Photon
Photon 是 Databricks 為湖倉一體環境開發的新型向量化查詢引擎,以下是 Photon 轉向原生 Native 執行的關鍵原因和實現方式:
- 原生 C++ 實現:Photon 選擇用 C++ 實現,而不是沿用基於 JVM 的Databricks Runtime(DBR)。原生代碼避免了 JVM 的性能瓶頸,如JIT編譯器的限制和垃圾回收問題。Photon 通過顯式控制內存管理和SIMD指令,顯著提升了連接、聚合和表達式評估的性能。
- 向量化執行模型:Photon 採用解釋型向量化執行,而非 Spark SQL 的代碼生成模型。向量化通過批處理數據分攤函數調用開銷,利用 SIMD 提高性能。
- 內存管理優化:Photon 通過內部緩衝池管理內存分配,避免昂貴的操作系統級分配。對於持久性分配(如聚合或連接),Photon 與 Spark 的統一內存管理器集成,支持動態溢出機制。這種靈活性在處理湖倉一體中常見的超大記錄時尤為重要。
- 與 Spark 的兼容性:儘管轉向原生執行,Photon 通過 JNI 與 Spark 集成,支持部分查詢在 Photon 和 Spark SQL 之間切換,確保語義一致性。這種增量部署策略降低了遷移成本,同時保留了 Spark 生態系統的優勢。
Photon 的成功驗證了原生向量化引擎在湖倉一體環境中的優越性,其在 100TB TPC-DS 基準測試中的世界紀錄進一步證明了其性能優勢。
三. Gluten 和 Velox
Apache Gluten 是由 Intel 和 Kyligence 發起的一箇中間層組件,它的主要職責在於將基於 JVM 的SQL 引擎的執行任務卸載到原生 Native 引擎上進行處理,以此顯著提升數據處理速度並降低資源消耗。如下圖所示,Gluten 作為中間層,上游對接 Spark 或者其他大數據計算框架,下游執行層則對接 Velox,Clickhouse 這類本地高性能計算引擎。
Velox 是 Meta 開源的一款 C++ 實現的向量化執行引擎,簡單説就是一個單機/單節點的 C++ 的向量化 runtime 模塊的實現,裏面包括了數據類型,函數,表達式,aggregate function,operator,I/O等的向量化實現,用於替換 Spark/Presto 的 runtime 部分,使得這類計算引擎從 JVM 切換到 C++ 實現得以提速。
Gluten+Velox 的組合,讓Spark/Presto 也可以像等Native引擎一樣發揮向量化執行的性能優勢。
四. Spark/Flink向量化
Spark向量化
目前,國內外業界主流各大互聯網公司對 Spark 多通過 JNI 的方式直接在大數據量的情況下以 Gluten+Velox 的形式進行 native 算子庫加速。比如英特爾公司推出的Gluten,通過 Fallback 機制,即當查詢計劃不能執行,或者有程序崩潰時,也能保證任務執行。因為項目初期功能表現欠佳,現階段與 Spark JVM 協作,當有算子或是功能支持失敗時,就會回退到 JVM 執行,以保證查詢計劃的執行成功。
Flink向量化引擎-Flash
阿里雲也推出了向量化版本 Flink 引擎-- Flash,其中性能數據顯示,相較於開源的 Flink 版本,Flash 引擎性能提升了5到10倍。如下圖所示,Flash 通過中間一層 Leno 膠水層,它類似於 Spark 中的 Gluten,主要負責將流式 Native Runtime 與 Flink 的分佈式框架解耦。這樣,在之前的 Java 算子版本上,可以獨立發佈 Native 算子。Leno 膠水層的任務是生成 Native 的執行計劃,即根據用户的 SQL 需求,通過 Flink Planner 判斷 SQL 語句中算子是否全部被覆蓋。如果全部覆蓋,就生成完整的 C++ 向量化執行計劃;如果不行,則回退到 Java 的執行計劃。
五. Native 向量化引擎
業界也有一些成熟的 Native 向量化引擎,如 StarRocks。StarRocks 通過實現全面向量化引擎,充分發揮了 CPU 的處理能力。全面向量化引擎按照列式的方式組織和處理數據。StarRocks 的數據存儲、內存中數據的組織方式,以及 SQL 算子的計算方式,都是列式實現的。按列的數據組織也會更加充分的利用 CPU 的 Cache,按列計算會有更少的虛函數調用以及更少的分支判斷從而獲得更加充分的 CPU 指令流水。
另一方面,StarRocks 的全面向量化引擎通過向量化算法充分的利用 CPU 提供的 SIMD(Single Instruction Multiple Data)指令。這樣 StarRocks 可以用更少的指令數目,完成更多的數據操作。經過標準測試集的驗證,StarRocks 的全面向量化引擎可以將執行算子的性能,整體提升 3~10 倍。
StarRocks BE 端完全用 C++ 代碼實現,只有在涉及到一些外表 Paimon/Hive, 會通過 JNI 的方式進行交互,這也側面反映 Java 生態的強大。
六. 未來和挑戰
C++ 的複雜性和 Java 強大的生態:過去十幾年的絕大部份大數據框架都是基於 Java 語言,JVM 生態系統在大數據領域根深蒂固,原生引擎需要通過 JNI 與現有周邊工具集成,增加了開發複雜性。
大數據計算引擎可能繼續向混合模型演進。例如,Spark 和 Flink 可能在保留 JVM 生態的同時,逐步將關鍵算子遷移到原生實現。而 Native 原生引擎可能成為高性能 OLAP 和湖倉一體場景的主流選擇。此外,隨着硬件加速器(如GPU和TPU)的普及,引擎可能進一步優化以利用這些新型計算資源得到提速。