Arroyo:基於Arrow和DataFusion的新SQL引擎

banq發表於2024-03-20


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)
    .source(KafkaSource::new(<font>"localhost:9092", "events", HashMap::new()))
    .watermark(WatermarkType::Periodic {
        period: Duration::from_secs(1),
        max_lateness: Duration::ZERO,
    })
    .flat_map(|event| {
            event.split(
" ").map(|w| (w.to_string(), 1)).collect()
    })
    .key_by(|(word, _)| word)
    .window(TumblingWindow::new(Duration::from_secs(1)))
    .sum_by(|(_, count)| count)
        .sink(KeyedConsoleSink::new());


但與 JVM 相比,Rust 這樣的編譯語言在這裡提出了一些挑戰。在 Flink 中,管道是用 Java 或 Scala 編寫的,編譯為 JVM 位元組碼,然後動態載入到 Flink 執行時中。但這種方法不太適合 Rust,它需要靜態編譯的二進位制檔案。

相反,我們將 Arroyo 執行時構建為一個庫,它將由實際的管道程式碼呼叫。然後,所有內容都將被編譯成靜態二進位制檔案,該二進位制檔案將執行管道。

新增SQL
在 Lyft,SQL 是我們流媒體平臺的潛在使用者最需要的功能。雖然 Flink 有 SQL API,但使用者測試表明它仍然太混亂,並且需要太多 Flink 專業知識,非流媒體專家才能掌握。

因此,我們從一開始就知道我們需要出色的 SQL 實現。我們希望瞭解 SQL 的資料工程師和科學家能夠構建正確、可靠且高效能的流管道,而無需太多構建流系統的專業知識。

當我們開始構建 SQL 介面時,我們不想從頭開始。因此,我們轉向了 DataFusion,它既是一個完整的批處理 SQL 引擎,也是一個可組合的 SQL 原語庫。我們決定只使用前端,它透過幾個階段獲取使用者提供的原始字串 SQL:

  1. 解析,將 SQL 文字轉換為抽象語法樹 (AST)
  2. Planning,將 AST 轉換為SQL 運算子的邏輯圖,並具有它們之間的資料依賴關係
  3. 最佳化,將各種重寫規則應用於圖形以簡化圖形並使其執行效率更高

一旦我們有了最佳化的圖,我們就把它轉換成我們自己的資料流表示(物理圖),我們將在執行時執行。

本質上,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 個事件,那麼無論如何,處理的總體成本都將非常小。在高資料量(每秒數萬到數百萬個事件)的情況下,我們可以魚與熊掌兼得——透過批處理和列式資料實現高吞吐量,同時仍然保持較低的絕對延遲。

詳細點選標題

相關文章