在 Apache Arrow 中,一張資料表不是一個模糊的「表格物件」,而是由三個核心概念組成:
Schema
→ 定義欄位名稱、資料型別與欄位順序
Array
→ 儲存單一欄位、相同型別的一串資料
RecordBatch
→ 將多個等長 Array 組成一批欄式表格資料
可以把它們想成:
Schema 是表格的設計圖
Array 是每一欄實際存放的資料
RecordBatch 是一小批可以交給 Query Engine 處理的表格
Day 8 提到,DataFusion 使用 Arrow 作為記憶體中的欄式資料格式。今天要進一步拆解:當資料進入記憶體後,它究竟長什麼樣子?
假設我們有一張訂單表:
| order_id | city | amount | is_member |
|---|---|---|---|
| 1 | 台北 | 1200.0 | true |
| 2 | 台中 | 850.0 | false |
| 3 | 台北 | 2300.0 | true |
在 Arrow 中,這張表可以拆成 Schema 與多個 Array,最後再組合成一個 RecordBatch。

圖 1:Apache Arrow 中 Schema、Array 與 RecordBatch 的關係。圖由本文整理。
接下來分別拆解這三個核心資料結構。
Schema 描述一批資料「應該長什麼樣子」。
以上面的訂單資料為例,Schema 可以概念化成:
order_id : Int64
city : Utf8
amount : Float64
is_member : Boolean
Schema 至少告訴我們三件事:
因此,Schema 不會保存實際訂單資料;它描述的是資料的結構與規則。
可以把它想成資料表的設計圖:
Schema
├── order_id:64 位元整數
├── city:UTF-8 字串
├── amount:64 位元浮點數
└── is_member:布林值
當 Query Engine 讀取 CSV、JSON 或 Parquet 時,需要知道每個欄位的型別,才能正確進行篩選、比較、聚合與 Join。
例如:
SELECT SUM(amount)
FROM orders;
SUM() 需要知道 amount 是可以計算的數值型別;若它其實是字串,就需要先轉型,或直接回傳錯誤。
在 Arrow 裡,Array 是一串已知長度、而且型別相同的值。
例如訂單資料的 city 欄位:
city Array
[台北, 台中, 台北]
amount 欄位則是另一個 Array:
amount Array
[1200.0, 850.0, 2300.0]
每個欄位都會有自己的 Array:
order_id Array : [1, 2, 3]
city Array : [台北, 台中, 台北]
amount Array : [1200.0, 850.0, 2300.0]
is_member Array : [true, false, true]
這就是 Arrow 的欄式資料表示法:同一欄位的值會集中在一起,而不是將每筆完整資料排在一起。
Row-based
[1, 台北, 1200.0, true]
[2, 台中, 850.0, false]
[3, 台北, 2300.0, true]
Arrow 的 Columnar 表示
order_id : [1, 2, 3]
city : [台北, 台中, 台北]
amount : [1200.0, 850.0, 2300.0]
is_member : [true, false, true]
這也呼應 Day 7 的內容:分析查詢通常只需要其中幾個欄位,因此欄式資料可以讓 Query Engine 集中處理真正需要的資料。
單一 Array 只能表示一個欄位;一張表格則需要多個欄位。
Arrow 會將多個 Array 組合成一個 RecordBatch:
RecordBatch
├── order_id Array : [1, 2, 3]
├── city Array : [台北, 台中, 台北]
├── amount Array : [1200.0, 850.0, 2300.0]
└── is_member Array : [true, false, true]
這裡有一個很重要的規則:
同一個 RecordBatch 中的所有 Array,長度必須相同。
因為各欄位的第 1 個值要組成第 1 列資料:
order_id[0] = 1
city[0] = 台北
amount[0] = 1200.0
is_member[0] = true
合起來才是:
[1, 台北, 1200.0, true]
同樣地,第 2 個位置會組成第 2 列:
[2, 台中, 850.0, false]
所以 RecordBatch 可以視為:
具有共同 Schema 的一小批表格資料。
實務上的資料量可能很大,一張表可能有數百萬甚至數十億列資料。
如果一次把整張表放進一個 RecordBatch:
一個超大的 RecordBatch
可能會消耗大量記憶體,也不利於串流式處理與平行執行。
因此,資料通常會切成多個批次:
orders
├── RecordBatch 1:前 8,192 列
├── RecordBatch 2:接續的 8,192 列
├── RecordBatch 3:接續的 8,192 列
└── ...
實際批次大小會依資料來源、設定與執行環境而不同;重點是 DataFusion 不需要等待整張表都讀進記憶體,便能開始處理前面的資料批次。
這樣的處理方式有幾個好處:
回到前幾天使用過的 SQL:
SELECT city, SUM(amount)
FROM orders
WHERE is_member = true
GROUP BY city;
假設 DataFusion 收到一個 RecordBatch:
order_id : [1, 2, 3]
city : [台北, 台中, 台北]
amount : [1200.0, 850.0, 2300.0]
is_member : [true, false, true]
它可以依序進行:
1. Filter
依 is_member = true 篩選資料
2. Projection
保留 city 與 amount
3. Aggregate
依 city 分組,計算 SUM(amount)
篩選後的概念結果是:
city : [台北, 台北]
amount : [1200.0, 2300.0]
接著再聚合:
台北 → 1200.0 + 2300.0 = 3500.0
這裡的關鍵是:DataFusion 的執行節點會接收或產生 Arrow RecordBatch,而不是一次只處理一筆資料列。
Schema 也扮演資料品質檢查的角色。
例如某份資料原本預期:
amount : Float64
但某一批資料出現:
amount : "unknown"
這就可能造成型別不符。
或者原本預期有四個欄位:
order_id
city
amount
is_member
但新資料少了 amount 欄位,也會讓後續的 SQL 無法正確執行。
因此,Schema 不只是描述資料的文件,它也是 Query Engine 理解資料、規劃查詢與及早發現問題的重要依據。
這也是資料工程常見的工作:在資料進入下游系統前,確認 Schema 是否符合預期。
NULL 在 Arrow 中怎麼表示?資料不完整是很常見的情況。
例如某筆訂單還沒有城市資料:
| order_id | city | amount | is_member |
|---|---|---|---|
| 1 | 台北 | 1200.0 | true |
| 2 | NULL |
850.0 | false |
| 3 | 台北 | 2300.0 | true |
概念上,city Array 可以表示為:
city: [台北, NULL, 台北]
Arrow 會額外使用一份 Validity Bitmap(有效性位元圖),記錄每個位置的值是否有效:
值: [台北, NULL, 台北]
有效性: [ 1, 0, 1]
1:這個位置有有效值
0:這個位置是 NULL
這樣 Arrow 不需要為每個型別都設計一個特殊的 NULL 值,也能用一致的方式處理缺失資料。
目前只要先記住概念即可;實際的 Bitmap、Buffer 與記憶體配置,之後深入 Arrow 的底層結構時再繼續拆解。
最後整理一次:
| 概念 | 它負責什麼? | 例子 |
|---|---|---|
| Schema | 定義欄位名稱、型別與順序 | amount: Float64 |
| Array | 儲存單一欄位的一串同型別值 | [1200.0, 850.0, 2300.0] |
| RecordBatch | 將多個等長 Array 組成一批表格資料 | 一批 3 列訂單資料 |
可以用一句話記住:
Schema 告訴我們資料應該長什麼樣子;
Array 保存每個欄位的實際值;
RecordBatch 把各欄位組成可被 Query Engine 處理的一批資料。
今天拆解了 Arrow 最重要的三個核心資料結構。
當資料進入 DataFusion 後,會以 Arrow 的欄式形式存在記憶體中:
Schema
↓
定義欄位規格
Array
↓
儲存每一欄資料
RecordBatch
↓
組成一批可運算的表格資料
這種設計讓 DataFusion 能一批一批執行 Filter、Projection、Aggregate 與 Join,也讓欄式資料處理更有效率。
明天會接著追蹤資料讀取流程:從 CSV、JSON、Parquet 等資料來源開始,資料如何一步步進入 DataFusion,最後變成 Arrow RecordBatch。