Arroyo 0.10 擁有一個使用 Apache Arrow 和 DataFusion 構建的全新 SQL 引擎。它更快、更小、更容易執行。
這篇文章將詳細介紹 Arroyo 當前的實現以及為什麼會發生變化,但簡而言之:
- 效能:Arrow 是一種記憶體中列格式,旨在利用現代 CPU 的向量處理能力;與高效能運算核心相結合,我們可以實現最先進的流媒體效能,可與最好的批處理引擎競爭
- 架構簡單:今天,Arroyo 生成 Rust 程式碼,然後將其編譯成執行資料處理的管道二進位制檔案。提前編譯提供了良好的效能,但需要複雜的基礎設施來編譯管道。Arroyo 0.10 作為單個緊湊的二進位制檔案提供,可以透過多種方式進行部署。
- 社群:Arroyo 正在迅速成為下一代資料堆疊的中心,透過採用它,Arroyo 可以與其他資料系統甚至其他語言(例如 Python)無縫互動。透過完全採用 DataFusion,我們能夠利用(並貢獻)新興 Rust 資料生態系統的出色工作。
由於資料人員喜歡數字,以下是與 Arroyo 0.9 的一些比較:
- 吞吐量:提高 3 倍
- 管道啟動:速度提高 20 倍
- Docker 映象大小:小 11 倍
截至今天,Arroyo 0.10 已作為開發者預覽版提供。您可以透過執行 docker 容器開始:
docker run -it -p 8000:8000 ghcr.io/arroyosystems/arroyo-single:0.10.0-dev
那麼我們是如何走到這一步的,為什麼我們現在要做出這樣的改變呢?讓我們回顧一下 Arroyo 的歷史,並介紹構建 SQL 引擎時的一些設計決策。
靈感由來
靈感來自於我在 Lyft 和 Splunk 領導 Apache Flink團隊的經歷。我親眼目睹了開發 Flink 流處理管道的挑戰以及操作的難度。在資料生態系統中,像 Redpanda 和 ScyllaDB 這樣的專案成功地重新思考了現有的 Java 系統,用非託管語言實現了更簡單、更高效能的實現,我認為 Flink 也有機會做同樣的事情。
Arroyo 的最初原型以 Flink 作為直接靈感。我們的新系統將用更快的語言(Rust)編寫,並將修復它的一些缺點,特別是在狀態方面。
其他方面我們最初保持不變。例如,Flink 的核心 API 稱為 Datastream API。它是一個用於直接定義 資料流圖的Java API 。這是一個有向非迴圈圖,資料訊息在其邊緣上流動,在實現查詢邏輯各個部分(如過濾器、連線或視窗)的運算子之間流動。
在我們最初的 Arroyo 原型中,該圖同樣是透過 Rust API 直接定義的。
Stream::<()>::with_parallelism(1) |
但與 JVM 相比,Rust 這樣的編譯語言在這裡提出了一些挑戰。在 Flink 中,管道是用 Java 或 Scala 編寫的,編譯為 JVM 位元組碼,然後動態載入到 Flink 執行時中。但這種方法不太適合 Rust,它需要靜態編譯的二進位制檔案。
相反,我們將 Arroyo 執行時構建為一個庫,它將由實際的管道程式碼呼叫。然後,所有內容都將被編譯成靜態二進位制檔案,該二進位制檔案將執行管道。
新增SQL
在 Lyft,SQL 是我們流媒體平臺的潛在使用者最需要的功能。雖然 Flink 有 SQL API,但使用者測試表明它仍然太混亂,並且需要太多 Flink 專業知識,非流媒體專家才能掌握。
因此,我們從一開始就知道我們需要出色的 SQL 實現。我們希望瞭解 SQL 的資料工程師和科學家能夠構建正確、可靠且高效能的流管道,而無需太多構建流系統的專業知識。
當我們開始構建 SQL 介面時,我們不想從頭開始。因此,我們轉向了 DataFusion,它既是一個完整的批處理 SQL 引擎,也是一個可組合的 SQL 原語庫。我們決定只使用前端,它透過幾個階段獲取使用者提供的原始字串 SQL:
- 解析,將 SQL 文字轉換為抽象語法樹 (AST)
- Planning,將 AST 轉換為SQL 運算子的邏輯圖,並具有它們之間的資料依賴關係
- 最佳化,將各種重寫規則應用於圖形以簡化圖形並使其執行效率更高
一旦我們有了最佳化的圖,我們就把它轉換成我們自己的資料流表示(物理圖),我們將在執行時執行。
本質上,SQL 支援位於現有圖形 API 之上。這實際上與 Flink SQL 的工作原理非常相似——SQL 由外部庫(Apache Calcite)解析和規劃,然後編譯成 Flink Datastream 程式。
一旦確定了這個基本方法,我們就必須做出另外兩個設計決策,這將決定我們未來的開發:如何表示 SQL 資料行以及如何實現 SQL 表示式。
在列上流式傳輸
在過去的十年中,幾乎所有 OLAP(面向分析)引擎都採用了列表示5 。造成這種情況的原因有以下幾個:
- 透過將所有值儲存在一列中,您可以獲得更好的壓縮比並更好地利用 CPU 快取
- 只需要讀取查詢中實際引用的列,減少磁碟和網路IO
- 列式處理與現代 CPU 中的向量功能非常契合,可提供 10 倍或更多的加速
然而,面向行的資料仍然是流引擎的標準。延遲(事件透過管道的速度)和吞吐量(給定數量的 CPU 可以處理多少個事件)之間存在一些固有的權衡。透過批處理資料,我們可以以延遲為代價獲得更高的吞吐量。列式表示要求我們在看到效能改進之前將多個事件一起批處理(事實上,由於固定開銷,行數較少的列式處理會慢得多)。
Flink 和 Arroyo 等流引擎在邏輯上一次對一個事件進行操作,並圍繞有序處理提供重要保證。最初,它們是逐一對事件進行物理操作的。但批處理的好處不容忽視,最新版本的 Flink 確實 支援 SQL 運算子中的某些批處理[url=https://www.arroyo.dev/blog/why-arrow-and-datafusionuser-content-fn-5]6[/url]。
但我認為批處理在流處理中有意義的理由很簡單: 在任何給定的批處理大小下,吞吐量越高,我們必須等待填充該批處理的時間就越少。例如,如果我們希望批次中至少有 100 條記錄來克服固定成本,則等待接收 100 條記錄所需的時間將取決於我們的吞吐量:
- 如果每秒 10 個事件,則需要 1 秒
- 1,000 時 — 0.01 秒 (100 毫秒)
- 1,000,000 時 — 0.0001 (0.1ms)
或者從固定延遲的角度來看(例如,最多等待 10 毫秒):
- 在每秒 10 個事件時,我們的批次大小為 1
- 1,000 — 100
- 1,000,000 — 100,000
結論是:當我們的資料量非常低時,我們只需為小批次大小支付高額開銷。但如果我們每秒只處理 10 或 100 個事件,那麼無論如何,處理的總體成本都將非常小。在高資料量(每秒數萬到數百萬個事件)的情況下,我們可以魚與熊掌兼得——透過批處理和列式資料實現高吞吐量,同時仍然保持較低的絕對延遲。
詳細點選標題