Apache Arrow 是一套跨語言的欄式記憶體格式。
它不是資料庫,也不是只用來保存資料的檔案格式;它處理的是另一個問題:
資料進入記憶體後,要如何排列,
才能讓 Query Engine 有效率地處理?
在這個系列裡,可以先用三句話理解它們的分工:
Parquet:資料如何儲存在檔案中
Apache Arrow:資料如何排列在記憶體中
Apache DataFusion:如何規劃並執行查詢
Day 6 介紹了 Parquet 這類欄式檔案格式;Day 7 說明分析系統為什麼偏好欄式資料。今天則要接著認識:當資料被讀進記憶體後,DataFusion 如何表示與處理它。
答案就是 Apache Arrow。
假設我們執行以下 SQL:
SELECT city, SUM(amount)
FROM orders
WHERE is_member = true
GROUP BY city;
原始資料可能存放在 CSV、JSON 或 Parquet 檔案中。
但是,Query Engine 不能只停留在「讀到檔案」這一步;它還需要在記憶體中完成:
Filter
→ 篩選 is_member = true
Projection
→ 保留 city、amount
Aggregate
→ 依 city 分組並計算 SUM(amount)
因此,除了檔案格式以外,還需要一個適合分析運算的記憶體格式。

圖 1:資料從檔案讀入 Arrow RecordBatch,再由 DataFusion 規劃與執行查詢的流程。圖由本文整理。
可以先把這個流程理解成:
CSV / JSON / Parquet
↓
Apache Arrow RecordBatch
↓
Apache DataFusion
↓
Filter / Projection / Aggregate / Join
↓
Query Result
以 Parquet 為例,DataFusion 可以讀取需要的資料欄位與區塊,再以 Arrow 的欄式資料批次交給後續執行節點處理。
這裡最重要的差別是:
Parquet 是磁碟上的格式。
Arrow 是記憶體中的格式。
兩者都採欄式設計,但解決的是不同階段的問題。
Apache Arrow 是一個開源專案,提供多種程式語言可共同使用的資料格式與函式庫。
它的核心是:
以欄式方式,在記憶體中表示表格資料。
假設有一張訂單表:
| order_id | city | amount | is_member |
|---|---|---|---|
| 1 | 台北 | 1200 | true |
| 2 | 台中 | 850 | false |
| 3 | 台北 | 2300 | true |
在 Arrow 中,可以概念化成:
order_id : [1, 2, 3]
city : [台北, 台中, 台北]
amount : [1200, 850, 2300]
is_member: [true, false, true]
每個欄位都由相同型別的值組成,並以欄為單位排列。
這和 Day 7 介紹的 Columnar 概念一致:若查詢只需要部分欄位,Query Engine 就能集中處理那些欄位,而不必處理整筆資料中的所有內容。
Arrow 與 Parquet 都和欄式資料有關,但它們不應被視為同一件事。
| 面向 | Apache Parquet | Apache Arrow |
|---|---|---|
| 主要位置 | 磁碟、物件儲存、資料湖 | 記憶體 |
| 主要目的 | 儲存資料、壓縮資料、減少讀取量 | 高效率處理與傳遞資料 |
| 常見概念 | Row Group、Column Chunk、Metadata | Array、RecordBatch、Buffer |
| 使用時機 | 資料尚未讀進 Query Engine | 資料已進入 Query Engine |
可以用一個生活化的比喻:
Parquet 像是倉庫中的收納方式。
Arrow 像是工作桌上的擺放方式。
Parquet 希望資料存得省空間、讀得更少;Arrow 希望資料一進入記憶體,就能被 CPU 與 Query Engine 有效率地處理。
在 Arrow 中,Array 是一串型別相同的值。
例如:
amount: [1200, 850, 2300]
這是一個數值型別的 Array。
city: [台北, 台中, 台北]
這是一個字串型別的 Array。
is_member: [true, false, true]
這是一個布林型別的 Array。
Arrow 不只支援整數、浮點數、字串與布林值,也支援日期、時間、Decimal、List、Struct、Map 等型別。
單一 Array 只能表示一個欄位;一張表格通常有多個欄位。
Arrow 會將多個、且列數相同的 Array 組合成一個 RecordBatch:
RecordBatch
├── order_id : [1, 2, 3]
├── city : [台北, 台中, 台北]
├── amount : [1200, 850, 2300]
└── is_member: [true, false, true]
可以先將 RecordBatch 想成:
一小批欄式資料表。
它包含:
DataFusion 執行查詢時,通常會一批一批處理 RecordBatch,而不是一次只處理一列資料。
Arrow 的效能來自幾個設計特性。
當 DataFusion 要執行:
SUM(amount)
它可以連續處理:
[1200, 850, 2300, 560, 1750, ...]
而不需要在每筆資料中跳過城市、日期、使用者名稱或其他不相關欄位。
Arrow 將資料分成一批一批的 RecordBatch。
一次處理一列
→ 讀取、判斷、計算,再處理下一列
一次處理一批
→ 讀取一組欄位值,再一起篩選或計算
這使 Query Engine 更容易進行高效率的欄式運算。
若每個工具都有自己的記憶體格式,資料在系統間傳遞時,常需要反覆轉換:
系統 A 的格式
→ 序列化
→ 傳輸
→ 反序列化
→ 系統 B 的格式
Arrow 提供標準化的資料格式後,支援 Arrow 的工具能更容易交換資料。
這也是 Arrow 的另一個價值:它不只是讓單一系統跑得更快,也讓不同語言與資料工具之間更容易合作。
初學 Arrow 時,常會聽到 Zero Copy(零拷貝)這個詞。
它不是指所有操作都不會複製資料,而是 Arrow 的標準化記憶體格式,讓部分情境可以直接共享或傳遞資料 Buffer,避免額外的序列化與複製。
是否能真正避免複製,仍取決於:
因此,更精確的說法是:
Arrow 讓減少資料複製成為可能,
但不保證所有操作都不會複製資料。
DataFusion 是用 Rust 實作的 Query Engine,而 Arrow 提供了它處理資料時的共同格式。
一段 SQL 在 DataFusion 中,可以先簡化為:
SQL
↓
Logical Plan
↓
Physical Plan
↓
Execution Plan
↓
Arrow RecordBatch
↓
Result
Filter、Projection、Aggregate 與 Join 等執行節點,都會讀取或產生 Arrow RecordBatch。
這讓 DataFusion 能把重點放在:
而不必為每個執行元件重新設計一種記憶體資料格式。
今天認識了 Apache Arrow 在 DataFusion 中的角色。
可以用三句話總結:
Parquet 負責把欄式資料儲存在檔案中。
Arrow 負責把欄式資料排列在記憶體中。
DataFusion 負責使用 Arrow 規劃並執行查詢。
Arrow 的 Array 與 RecordBatch,讓 DataFusion 能以欄式、批次化的方式處理資料,為後續的 Filter、Projection、Aggregate 與 Join 打下基礎。
明天會繼續拆解 Arrow 最重要的三個核心資料結構:
Schema
Array
RecordBatch