Arrow 欄狀格式#

版本:1.5

Arrow 欄狀格式 包含與語言無關的記憶體內資料結構規格、metadata 序列化,以及用於序列化和通用資料傳輸的協定。

本文旨在提供足夠的細節,以便在沒有現有實作的輔助下,建立欄狀格式的新實作。我們使用 Google 的 Flatbuffers 專案進行 metadata 序列化,因此在閱讀本文時,有必要參考該專案的 Flatbuffers 協定定義檔

欄狀格式有一些關鍵特性

  • 資料鄰近性,適用於循序存取(掃描)

  • O(1)(常數時間)隨機存取

  • SIMD 和向量化友善

  • 可重新定位,無需「指標調整」,允許在共享記憶體中實現真正的零複製存取

Arrow 欄狀格式提供分析效能和資料局部性保證,以換取相對昂貴的變更操作。本文僅關注記憶體內資料表示和序列化細節;諸如協調資料結構變更等問題留待實作處理。

術語#

由於不同的專案使用不同的詞語來描述各種概念,因此這裡提供一個小詞彙表以幫助消除歧義。

  • 陣列向量:具有已知長度且所有值都具有相同類型的數值序列。這些術語在不同的 Arrow 實作中可以互換使用,但在本文中我們使用「陣列」。

  • 槽位:某種特定資料類型陣列中的單個邏輯值

  • 緩衝區連續記憶體區域:具有給定長度的循序虛擬位址空間。任何位元組都可以通過小於區域長度的單個指標偏移量來存取。

  • 實體佈局:陣列的底層記憶體佈局,不考慮任何值語義。例如,32 位元有號整數陣列和 32 位元浮點數陣列具有相同的佈局。

  • 資料類型:面向應用程式的語義值類型,使用某些實體佈局實作。例如,Decimal128 值以固定大小二進制佈局儲存為 16 個位元組。時間戳記可以儲存為 64 位元固定大小佈局。

  • 基本類型:沒有子類型的資料類型。這包括固定位元寬度、可變大小二進制和空值類型等類型。

  • 巢狀類型:其完整結構取決於一個或多個其他子類型的資料類型。當且僅當兩個完全指定的巢狀類型的子類型相等時,它們才相等。例如,List<U>List<V> 不同,前提是 U 和 V 是不同的類型。

  • 父陣列子陣列:用於表達巢狀類型結構中實體值陣列之間關係的名稱。例如,List<T> 類型的父陣列具有 T 類型的陣列作為其子陣列(請參閱下文關於列表的更多資訊)。

  • 參數化類型:一種需要額外參數才能完全確定其語義的類型。例如,所有巢狀類型在建構上都是參數化的。時間戳記也是參數化的,因為它需要單位(例如微秒)和時區。

資料類型#

Schema.fbs 檔案定義了 Arrow 欄狀格式支援的內建資料類型。每種資料類型都使用明確定義的實體佈局。

Schema.fbs 是標準 Arrow 資料類型描述的權威來源。但是,為了方便起見,我們也提供下表

類型

類型參數 (1)

實體記憶體佈局

Null

Null

布林值

固定大小基本類型

整數

  • 位元寬度

  • 有號性

「」(同上)

浮點數

  • 精度

「」

十進制

  • 位元寬度

  • 小數位數

  • 精度

「」

日期

  • 單位

「」

時間

  • 位元寬度 (2)

  • 單位

「」

時間戳記

  • 單位

  • 時區

「」

間隔

  • 單位

「」

持續時間

  • 單位

「」

固定大小二進制

  • 位元組寬度

固定大小二進制

二進制

具有 32 位元偏移量的可變大小二進制

Utf8

「」

大型二進制

具有 64 位元偏移量的可變大小二進制

大型 Utf8

「」

二進制視圖

可變大小二進制視圖

Utf8 視圖

「」

固定大小列表

  • 值類型

  • 列表大小

固定大小列表

列表

  • 值類型

具有 32 位元偏移量的可變大小列表

大型列表

  • 值類型

具有 64 位元偏移量的可變大小列表

列表視圖

  • 值類型

具有 32 位元偏移量和大小的可變大小列表視圖

大型列表視圖

  • 值類型

具有 64 位元偏移量和大小的可變大小列表視圖

Struct

  • 子欄位

Struct

Map

  • 子欄位

  • 鍵排序

Struct 的可變大小列表

Union

  • 子欄位

  • 模式

  • 類型 ID

密集或稀疏 Union (3)

字典

  • 索引類型 (4)

  • 值類型

  • 排序

字典編碼

行程長度編碼

  • 行程結束類型 (5)

  • 值類型

行程長度編碼

  • (1) 以斜體列出的類型參數表示資料類型的子類型。

  • (2) Time 類型的位元寬度參數在技術上是多餘的,因為每個單位都強制規定單一位元寬度。

  • (3) Union 類型是否使用稀疏或密集佈局由其模式參數表示。

  • (4) 字典類型的索引類型只能是整數類型,最好是有號類型,寬度為 8 到 64 位元。

  • (5) 行程長度編碼類型的行程結束類型只能是有號整數類型,寬度為 16 到 64 位元。

注意

有時術語「邏輯類型」用於表示 Arrow 資料類型,並將其與各自的實體佈局區分開來。但是,與 Apache Parquet 等其他類型系統不同,Arrow 類型系統沒有單獨的實體類型和邏輯類型概念。

Arrow 類型系統單獨提供 擴充類型,它允許使用更豐富的面向應用程式的語義來註解標準 Arrow 資料類型(例如,定義基於標準字串資料類型的「JSON」類型)。

實體記憶體佈局#

陣列由一些 metadata 和資料定義

  • 資料類型。

  • 緩衝區序列。

  • 長度,以 64 位元有號整數表示。實作允許限制為 32 位元長度,詳情請參閱下文。

  • 空值計數,以 64 位元有號整數表示。

  • 可選的 字典,用於字典編碼的陣列。

巢狀陣列另外還有一組或多組這些項目的序列,稱為 子陣列

每種資料類型都有明確定義的實體佈局。以下是 Arrow 定義的不同實體佈局

  • 基本(固定大小):每個值都具有相同位元組或位元寬度的數值序列

  • 可變大小二進制:每個值都具有可變位元組長度的數值序列。此佈局的兩種變體支援使用 32 位元和 64 位元長度編碼。

  • 可變大小二進制視圖:每個值都具有可變位元組長度的數值序列。與可變大小二進制相反,此佈局的值分佈在可能的多個緩衝區中,而不是密集且循序地封裝在單個緩衝區中。

  • 固定大小列表:一種巢狀佈局,其中每個值都具有相同數量的元素,這些元素取自子資料類型。

  • 可變大小列表:一種巢狀佈局,其中每個值都是取自子資料類型的可變長度數值序列。此佈局的兩種變體支援使用 32 位元和 64 位元長度編碼。

  • 可變大小列表視圖:一種巢狀佈局,其中每個值都是取自子資料類型的可變長度數值序列。此佈局與 可變大小列表 的不同之處在於,它具有一個額外的緩衝區,其中包含每個列表值的大小。這消除了對偏移量緩衝區的限制 — 它不需要按順序排列。

  • Struct:一種巢狀佈局,由命名子 欄位 的集合組成,每個欄位都具有相同的長度,但類型可能不同。

  • 稀疏密集 Union:一種巢狀佈局,表示數值序列,每個數值都可以從子陣列類型集合中選擇類型。

  • 字典編碼:一種佈局,由整數序列(任何位元寬度)組成,這些整數表示字典的索引,字典可以是任何類型。

  • 行程長度編碼 (REE):一種巢狀佈局,由兩個子陣列組成,一個表示值,另一個表示對應值的行程結束的邏輯索引。

  • Null:所有空值的序列。

Arrow 欄狀記憶體佈局僅適用於資料,而不適用於 metadata。實作可以自由地以對他們方便的任何形式在記憶體中表示 metadata。我們使用 Flatbuffers 以與實作無關的方式處理 metadata 序列化,詳情如下。

緩衝區對齊和填充#

建議實作在對齊的位址(8 位元組或 64 位元組的倍數)上分配記憶體,並填充(過度分配)到長度為 8 或 64 位元組的倍數。在序列化 Arrow 資料以進行跨進程通訊時,將強制執行這些對齊和填充要求。如果可能,我們建議您優先使用 64 位元組對齊和填充。除非另有說明,否則填充的位元組不需要具有特定值。

對齊要求遵循最佳實務,以實現最佳化的記憶體存取

  • 數值陣列中的元素將保證通過對齊的存取來檢索。

  • 在某些架構上,對齊可以幫助限制部分使用的快取行。

64 位元組對齊的建議來自 Intel 效能指南,該指南建議對齊記憶體以匹配 SIMD 暫存器寬度。選擇特定填充長度的原因是它與廣泛部署的 x86 架構(Intel AVX-512)上可用的最大 SIMD 指令暫存器相匹配。

建議的 64 位元組填充允許在迴圈中一致地使用 SIMD 指令,而無需額外的條件檢查。這應該可以實現更簡單、高效且 CPU 快取友善的程式碼。換句話說,我們可以將整個 64 位元組緩衝區載入到 512 位元寬的 SIMD 暫存器中,並在封裝到 64 位元組緩衝區中的所有欄狀值上獲得資料級平行處理。保證的填充還可以允許某些編譯器直接產生更最佳化的程式碼(例如,可以安全地使用 Intel 的 -qopt-assume-safe-padding)。

陣列長度#

陣列長度在 Arrow metadata 中表示為 64 位元有號整數。即使 Arrow 的實作僅支援最大 32 位元有號整數的長度,也被認為是有效的。如果在多語言環境中使用 Arrow,我們建議將長度限制為 2 31 - 1 個元素或更少。較大的資料集可以使用多個陣列區塊來表示。

空值計數#

空值槽位的數量是實體陣列的屬性,並被視為資料結構的一部分。空值計數在 Arrow metadata 中表示為 64 位元有號整數,因為它可能與陣列長度一樣大。

有效性位元圖#

陣列中的任何值在語義上都可能為空值,無論是基本類型還是巢狀類型。

所有陣列類型,除了 union 類型(稍後會詳細介紹),都使用專用的記憶體緩衝區,稱為有效性(或「空值」)位元圖,來編碼每個值槽位的空值或非空值狀態。有效性位元圖必須足夠大,才能為每個陣列槽位至少保留 1 位元。

任何陣列槽位是否有效(非空值)都編碼在此位元圖的相應位元中。索引 j 的 1(設定位元)表示該值不是空值,而 0(未設定位元)表示它是空值。位元圖應在分配時初始化為全部未設定(包括填充)

is_valid[j] -> bitmap[j / 8] & (1 << (j % 8))

我們使用 最低有效位元 (LSB) 編號(也稱為位元組序)。這表示在 8 位元組的群組中,我們從右到左讀取

values = [0, 1, null, 2, null, 3]

bitmap
j mod 8   7  6  5  4  3  2  1  0
          0  0  1  0  1  0  1  1

空值計數為 0 的陣列可以選擇不分配有效性位元圖;這如何表示取決於實作(例如,C++ 實作可以使用 NULL 指標來表示這種「不存在」的有效性位元圖)。實作可以選擇始終分配有效性位元圖,以方便起見。Arrow 陣列的使用者應準備好處理這兩種可能性。

巢狀類型陣列(除了上述 union 類型)具有自己的頂層有效性位元圖和空值計數,無論其子陣列的空值計數和有效位元如何。

空值的陣列槽位不需要具有特定值;任何「遮罩」記憶體都可以具有任何值,並且不需要歸零,儘管實作經常選擇將空值的記憶體歸零。

固定大小基本類型佈局#

基本值陣列表示一個數值陣列,每個值都具有相同的實體槽位寬度,通常以位元組為單位測量,儘管規範也提供了位元組封裝類型(例如,以位元編碼的布林值)。

在內部,陣列包含一個連續的記憶體緩衝區,其總大小至少與槽位寬度乘以陣列長度一樣大。對於位元組封裝類型,大小向上捨入到最接近的位元組。

關聯的有效性位元圖是連續分配的(如上所述),但不需要在記憶體中與值緩衝區相鄰。

佈局範例:Int32 陣列

例如,一個 int32 的基本陣列

[1, null, 2, 4, 8]

看起來像

* Length: 5, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00011101                 | 0 (padding)           |

* Value Buffer:

  | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63           |
  |-------------|-------------|-------------|-------------|-------------|-----------------------|
  | 1           | unspecified | 2           | 4           | 8           | unspecified (padding) |

佈局範例:非空值 int32 陣列

[1, 2, 3, 4, 8] 有兩種可能的佈局

* Length: 5, Null count: 0
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00011111                 | 0 (padding)           |

* Value Buffer:

  | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63           |
  |-------------|-------------|-------------|-------------|-------------|-----------------------|
  | 1           | 2           | 3           | 4           | 8           | unspecified (padding) |

或省略位元圖

* Length 5, Null count: 0
* Validity bitmap buffer: Not required
* Value Buffer:

  | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | bytes 12-15 | bytes 16-19 | Bytes 20-63           |
  |-------------|-------------|-------------|-------------|-------------|-----------------------|
  | 1           | 2           | 3           | 4           | 8           | unspecified (padding) |

可變大小二進制佈局#

此佈局中的每個值都由 0 個或多個位元組組成。雖然基本陣列具有單個值緩衝區,但可變大小二進制具有 偏移量 緩衝區和 資料 緩衝區。

偏移量緩衝區包含 length + 1 個有號整數(32 位元或 64 位元,取決於資料類型),這些整數編碼資料緩衝區中每個槽位的起始位置。每個槽位中值的長度是通過計算該槽位索引處的偏移量與後續偏移量之間的差值來計算的。例如,槽位 j 的位置和長度計算如下

slot_position = offsets[j]
slot_length = offsets[j + 1] - offsets[j]  // (for 0 <= j < length)

應該注意的是,空值可能具有正槽位長度。也就是說,空值可能佔用資料緩衝區中的 非空 記憶體空間。當這種情況為真時,相應記憶體空間的內容是未定義的。

偏移量必須單調遞增,即對於 0 <= j < lengthoffsets[j+1] >= offsets[j],即使對於空值槽位也是如此。此屬性確保所有值的位置都是有效且明確定義的。

通常,偏移量陣列中的第一個槽位為 0,最後一個槽位是值陣列的長度。在序列化此佈局時,我們建議將偏移量標準化為從 0 開始。

佈局範例:``VarBinary``

['joe', null, null, 'mark']

將表示如下

* Length: 4, Null count: 2
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00001001                 | 0 (padding)           |

* Offsets buffer:

  | Bytes 0-19     | Bytes 20-63           |
  |----------------|-----------------------|
  | 0, 3, 3, 3, 7  | unspecified (padding) |

 * Value buffer:

  | Bytes 0-6      | Bytes 7-63            |
  |----------------|-----------------------|
  | joemark        | unspecified (padding) |

可變大小二進制視圖佈局#

Arrow 版本新增:欄狀格式 1.4

此佈局中的每個值都由 0 個或多個位元組組成。這些位元組的位置使用 視圖 緩衝區指示,該緩衝區可以指向可能的多個 資料 緩衝區之一,或者可以內嵌包含字元。

視圖緩衝區包含 length 個視圖結構,佈局如下

* Short strings, length <= 12
  | Bytes 0-3  | Bytes 4-15                            |
  |------------|---------------------------------------|
  | length     | data (padded with 0)                  |

* Long strings, length > 12
  | Bytes 0-3  | Bytes 4-7  | Bytes 8-11 | Bytes 12-15 |
  |------------|------------|------------|-------------|
  | length     | prefix     | buf. index | offset      |

在長字串和短字串情況下,前四個位元組都編碼字串的長度,可用於確定應如何解釋視圖的其餘部分。

在短字串情況下,字串的位元組是內嵌的 — 儲存在視圖本身內,在長度後面的十二個位元組中。字串本身之後的任何剩餘位元組都用 0 填充。

在長字串情況下,緩衝區索引指示哪個資料緩衝區儲存資料位元組,偏移量指示資料位元組在該緩衝區中的起始位置。緩衝區索引 0 指的是第一個資料緩衝區,即位於有效性緩衝區和視圖緩衝區 之後 的第一個緩衝區。半開區間 [offset, offset + length) 必須完全包含在指示的緩衝區內。字串的前四個位元組的副本內嵌儲存在前綴中,位於長度之後。此前綴為字串比較啟用了一個有利可圖的快速路徑,字串比較通常在前四個位元組內確定。

所有整數(長度、緩衝區索引和偏移量)都是有號的。

此佈局改編自慕尼黑工業大學的 UmbraDB

請注意,此佈局使用一個額外的緩衝區來儲存在 Arrow C 資料介面 中的可變緩衝區長度。

可變大小列表佈局#

列表是一種巢狀類型,其語義與可變大小二進制相似。列表佈局有兩種變體 — 「列表」和「列表視圖」— 並且每個變體都可以通過 32 位元或 64 位元偏移量整數來分隔。

列表佈局#

列表佈局由兩個緩衝區、一個有效性位元圖和一個偏移量緩衝區以及一個子陣列定義。偏移量與可變大小二進制情況下的偏移量相同,並且 32 位元和 64 位元有號整數偏移量都是偏移量的支援選項。這些偏移量不是引用額外的資料緩衝區,而是引用子陣列。

與可變大小二進制的佈局類似,空值可能對應於子陣列中的 非空 段。當這種情況為真時,相應段的內容可以是任意的。

列表類型指定為 List<T>,其中 T 是任何類型(基本類型或巢狀類型)。在這些範例中,我們使用 32 位元偏移量,其中 64 位元偏移量版本將表示為 LargeList<T>

佈局範例:``List<Int8>`` 陣列

我們用長度為 4 的 List<Int8> 範例來說明,其值為

[[12, -7, 25], null, [0, -127, 127, 50], []]

將具有以下表示形式

* Length: 4, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00001101                 | 0 (padding)           |

* Offsets buffer (int32)

  | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63           |
  |------------|-------------|-------------|-------------|-------------|-----------------------|
  | 0          | 3           | 3           | 7           | 7           | unspecified (padding) |

* Values array (Int8Array):
  * Length: 7,  Null count: 0
  * Validity bitmap buffer: Not required
  * Values buffer (int8)

    | Bytes 0-6                    | Bytes 7-63            |
    |------------------------------|-----------------------|
    | 12, -7, 25, 0, -127, 127, 50 | unspecified (padding) |

佈局範例:``List<List<Int8>>``

[[[1, 2], [3, 4]], [[5, 6, 7], null, [8]], [[9, 10]]]

將表示如下

* Length 3
* Nulls count: 0
* Validity bitmap buffer: Not required
* Offsets buffer (int32)

  | Bytes 0-3  | Bytes 4-7  | Bytes 8-11 | Bytes 12-15 | Bytes 16-63           |
  |------------|------------|------------|-------------|-----------------------|
  | 0          |  2         |  5         |  6          | unspecified (padding) |

* Values array (`List<Int8>`)
  * Length: 6, Null count: 1
  * Validity bitmap buffer:

    | Byte 0 (validity bitmap) | Bytes 1-63  |
    |--------------------------|-------------|
    | 00110111                 | 0 (padding) |

  * Offsets buffer (int32)

    | Bytes 0-27           | Bytes 28-63           |
    |----------------------|-----------------------|
    | 0, 2, 4, 7, 7, 8, 10 | unspecified (padding) |

  * Values array (Int8):
    * Length: 10, Null count: 0
    * Validity bitmap buffer: Not required

      | Bytes 0-9                     | Bytes 10-63           |
      |-------------------------------|-----------------------|
      | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | unspecified (padding) |

ListView 佈局#

Arrow 版本新增:欄狀格式 1.4

ListView 佈局由三個緩衝區定義:有效性位元圖、偏移量緩衝區和額外的大小緩衝區。大小和偏移量具有相同的位元寬度,並且支援 32 位元和 64 位元有號整數選項。

與列表佈局一樣,偏移量編碼子陣列中每個槽位的起始位置。與列表佈局相反,列表長度顯式儲存在大小緩衝區中,而不是推斷出來的。這允許偏移量無序。子陣列的元素不必以它們在父陣列的列表元素中邏輯上出現的相同順序儲存。

每個列表視圖值(包括空值)都必須保證以下不變性

0 <= offsets[i] <= length of the child array
0 <= offsets[i] + size[i] <= length of the child array

列表視圖類型指定為 ListView<T>,其中 T 是任何類型(基本類型或巢狀類型)。在這些範例中,我們使用 32 位元偏移量和大小,其中 64 位元版本將表示為 LargeListView<T>

佈局範例:``ListView<Int8>`` 陣列

我們用長度為 4 的 ListView<Int8> 範例來說明,其值為

[[12, -7, 25], null, [0, -127, 127, 50], []]

它可能具有以下表示形式

* Length: 4, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00001101                 | 0 (padding)           |

* Offsets buffer (int32)

  | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-63           |
  |------------|-------------|-------------|-------------|-----------------------|
  | 0          | 7           | 3           | 0           | unspecified (padding) |

* Sizes buffer (int32)

  | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-63           |
  |------------|-------------|-------------|-------------|-----------------------|
  | 3          | 0           | 4           | 0           | unspecified (padding) |

* Values array (Int8Array):
  * Length: 7,  Null count: 0
  * Validity bitmap buffer: Not required
  * Values buffer (int8)

    | Bytes 0-6                    | Bytes 7-63            |
    |------------------------------|-----------------------|
    | 12, -7, 25, 0, -127, 127, 50 | unspecified (padding) |

佈局範例:``ListView<Int8>`` 陣列

我們繼續使用 ListView<Int8> 類型,但此實例說明了無序偏移量和子陣列值的共享。它是一個長度為 5 的陣列,具有邏輯值

[[12, -7, 25], null, [0, -127, 127, 50], [], [50, 12]]

它可能具有以下表示形式

* Length: 4, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00011101                 | 0 (padding)           |

* Offsets buffer (int32)

  | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63           |
  |------------|-------------|-------------|-------------|-------------|-----------------------|
  | 4          | 7           | 0           | 0           | 3           | unspecified (padding) |

* Sizes buffer (int32)

  | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-63           |
  |------------|-------------|-------------|-------------|-------------|-----------------------|
  | 3          | 0           | 4           | 0           | 2           | unspecified (padding) |

* Values array (Int8Array):
  * Length: 7,  Null count: 0
  * Validity bitmap buffer: Not required
  * Values buffer (int8)

    | Bytes 0-6                    | Bytes 7-63            |
    |------------------------------|-----------------------|
    | 0, -127, 127, 50, 12, -7, 25 | unspecified (padding) |

固定大小列表佈局#

固定大小列表是一種巢狀類型,其中每個陣列槽位都包含一個固定大小的數值序列,所有數值都具有相同的類型。

固定大小列表類型指定為 FixedSizeList<T>[N],其中 T 是任何類型(基本類型或巢狀類型),N 是表示列表長度的 32 位元有號整數。

固定大小列表陣列由值陣列表示,值陣列是 T 類型的子陣列。T 也可以是巢狀類型。固定大小列表陣列的槽位 j 中的值儲存在值陣列的 N 長切片中,從 j * N 的偏移量開始。

佈局範例:``FixedSizeList<byte>[4]`` 陣列

在這裡,我們用 FixedSizeList<byte>[4] 來說明。

對於長度為 4 的陣列,其值分別為

[[192, 168, 0, 12], null, [192, 168, 0, 25], [192, 168, 0, 1]]

將具有以下表示形式

* Length: 4, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00001101                 | 0 (padding)           |

* Values array (byte array):
  * Length: 16,  Null count: 0
  * validity bitmap buffer: Not required

    | Bytes 0-3       | Bytes 4-7   | Bytes 8-15                      |
    |-----------------|-------------|---------------------------------|
    | 192, 168, 0, 12 | unspecified | 192, 168, 0, 25, 192, 168, 0, 1 |

Struct 佈局#

Struct 是一種巢狀類型,由類型的有序序列(可以全部不同)參數化,稱為其欄位。每個欄位都必須具有 UTF8 編碼的名稱,並且這些欄位名稱是類型 metadata 的一部分。

在實體上,struct 陣列的每個欄位都有一個子陣列。子陣列是獨立的,不需要在記憶體中彼此相鄰。struct 陣列還具有有效性位元圖來編碼頂層有效性資訊。

例如,struct(此處顯示的欄位名稱為字串,僅供說明)

Struct <
  name: VarBinary
  age: Int32
>

有兩個子陣列,一個 VarBinary 陣列(使用可變大小二進制佈局)和一個 4 位元組基本值陣列,具有 Int32 邏輯類型。

佈局範例:``Struct<VarBinary, Int32>``

[{'joe', 1}, {null, 2}, null, {'mark', 4}] 的佈局,具有子陣列 ['joe', null, 'alice', 'mark'][1, 2, null, 4] 將會是

* Length: 4, Null count: 1
* Validity bitmap buffer:

  | Byte 0 (validity bitmap) | Bytes 1-63            |
  |--------------------------|-----------------------|
  | 00001011                 | 0 (padding)           |

* Children arrays:
  * field-0 array (`VarBinary`):
    * Length: 4, Null count: 1
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00001101                 | 0 (padding)           |

    * Offsets buffer:

      | Bytes 0-19     | Bytes 20-63           |
      |----------------|-----------------------|
      | 0, 3, 3, 8, 12 | unspecified (padding) |

     * Value buffer:

      | Bytes 0-11     | Bytes 12-63           |
      |----------------|-----------------------|
      | joealicemark   | unspecified (padding) |

  * field-1 array (int32 array):
    * Length: 4, Null count: 1
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00001011                 | 0 (padding)           |

    * Value Buffer:

      | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-63           |
      |-------------|-------------|-------------|-------------|-----------------------|
      | 1           | 2           | unspecified | 4           | unspecified (padding) |

Struct 有效性#

struct 陣列具有自己的有效性位元圖,該位元圖獨立於其子陣列的有效性位元圖。當一個或多個子陣列在其對應的槽位中具有非空值時,struct 陣列的有效性位元圖可能會指示空值;反之亦然,子陣列的有效性位元圖可能會指示空值,而 struct 陣列的有效性位元圖顯示非空值。

因此,要了解特定子項是否有效,必須對兩個有效性位元圖(struct 陣列的和子陣列的)中的相應位元執行邏輯 AND 運算。

這在上例中說明,其中一個子陣列對於空 struct 具有有效項 'alice',但它被 struct 陣列的有效性位元圖「隱藏」了。但是,當獨立處理時,子陣列的相應項將為非空值。

Union 佈局#

union 由類型的有序序列定義;union 中的每個槽位都可以具有從這些類型中選擇的值。這些類型的命名方式與 struct 的欄位類似,並且名稱是類型 metadata 的一部分。

與其他資料類型不同,union 沒有自己的有效性位元圖。相反,每個槽位的空值狀態完全由組合以建立 union 的子陣列確定。

我們定義了兩種不同的 union 類型:「密集」和「稀疏」,它們針對不同的用例進行了最佳化。

密集 Union#

密集 union 表示混合類型陣列,每個值有 5 個位元組的 overhead。其物理佈局如下

  • 每種類型一個子陣列

  • 類型緩衝區:一個 8 位元有號整數的緩衝區。union 中的每種類型都有一個對應的類型 ID,其值在此緩衝區中找到。具有超過 127 種可能類型的 union 可以建模為 union 的 union。

  • 偏移量緩衝區:一個有號 Int32 值的緩衝區,指示給定槽位中類型的相對於相應子陣列的偏移量。每個子值陣列的相應偏移量必須按順序/遞增排列。

佈局範例:``DenseUnion<f: Float32, i: Int32>``

對於 union 陣列

[{f=1.2}, null, {f=3.4}, {i=5}]

將具有以下佈局

* Length: 4, Null count: 0
* Types buffer:

  | Byte 0   | Byte 1      | Byte 2   | Byte 3   | Bytes 4-63            |
  |----------|-------------|----------|----------|-----------------------|
  | 0        | 0           | 0        | 1        | unspecified (padding) |

* Offset buffer:

  | Bytes 0-3 | Bytes 4-7   | Bytes 8-11 | Bytes 12-15 | Bytes 16-63           |
  |-----------|-------------|------------|-------------|-----------------------|
  | 0         | 1           | 2          | 0           | unspecified (padding) |

* Children arrays:
  * Field-0 array (f: Float32):
    * Length: 3, Null count: 1
    * Validity bitmap buffer: 00000101

    * Value Buffer:

      | Bytes 0-11     | Bytes 12-63           |
      |----------------|-----------------------|
      | 1.2, null, 3.4 | unspecified (padding) |


  * Field-1 array (i: Int32):
    * Length: 1, Null count: 0
    * Validity bitmap buffer: Not required

    * Value Buffer:

      | Bytes 0-3 | Bytes 4-63            |
      |-----------|-----------------------|
      | 5         | unspecified (padding) |

稀疏 Union#

稀疏 union 具有與密集 union 相同的結構,但省略了偏移量陣列。在這種情況下,子陣列的長度各自等於 union 的長度。

雖然與密集 union 相比,稀疏 union 可能會使用明顯更多的空間,但它有一些優勢,在某些用例中可能是理想的

  • 在某些用例中,稀疏 union 更適合向量化表達式評估。

  • 通過僅定義類型陣列,可以將等長陣列解釋為 union。

佈局範例:``SparseUnion<i: Int32, f: Float32, s: VarBinary>``

對於 union 陣列

[{i=5}, {f=1.2}, {s='joe'}, {f=3.4}, {i=4}, {s='mark'}]

將具有以下佈局

* Length: 6, Null count: 0
* Types buffer:

 | Byte 0     | Byte 1      | Byte 2      | Byte 3      | Byte 4      | Byte 5       | Bytes  6-63           |
 |------------|-------------|-------------|-------------|-------------|--------------|-----------------------|
 | 0          | 1           | 2           | 1           | 0           | 2            | unspecified (padding) |

* Children arrays:

  * i (Int32):
    * Length: 6, Null count: 4
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00010001                 | 0 (padding)           |

    * Value buffer:

      | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-23  | Bytes 24-63           |
      |-------------|-------------|-------------|-------------|-------------|--------------|-----------------------|
      | 5           | unspecified | unspecified | unspecified | 4           |  unspecified | unspecified (padding) |

  * f (Float32):
    * Length: 6, Null count: 4
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00001010                 | 0 (padding)           |

    * Value buffer:

      | Bytes 0-3    | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-23 | Bytes 24-63           |
      |--------------|-------------|-------------|-------------|-------------|-------------|-----------------------|
      | unspecified  | 1.2         | unspecified | 3.4         | unspecified | unspecified | unspecified (padding) |

  * s (`VarBinary`)
    * Length: 6, Null count: 4
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00100100                 | 0 (padding)           |

    * Offsets buffer (Int32)

      | Bytes 0-3  | Bytes 4-7   | Bytes 8-11  | Bytes 12-15 | Bytes 16-19 | Bytes 20-23 | Bytes 24-27 | Bytes 28-63            |
      |------------|-------------|-------------|-------------|-------------|-------------|-------------|------------------------|
      | 0          | 0           | 0           | 3           | 3           | 3           | 7           | unspecified (padding)  |

    * Values buffer:

      | Bytes 0-6  | Bytes 7-63            |
      |------------|-----------------------|
      | joemark    | unspecified (padding) |

僅考慮陣列中與類型索引對應的槽位。所有「未選中」的值都會被忽略,並且可以是任何語義上正確的陣列值。

Null 佈局#

我們為 Null 資料類型提供簡化的記憶體效率佈局,其中所有值都為空值。在這種情況下,不會分配記憶體緩衝區。

字典編碼佈局#

字典編碼是一種資料表示技術,用於通過引用 字典(通常由唯一值組成)的整數來表示值。當您的資料具有許多重複值時,它可能非常有效。

任何陣列都可以進行字典編碼。字典儲存為陣列的可選屬性。當欄位進行字典編碼時,這些值由非負整數陣列表示,這些整數表示字典中值的索引。字典編碼陣列的記憶體佈局與基本整數佈局相同。字典作為單獨的欄狀陣列處理,具有其自己的佈局。

例如,您可能有以下資料

type: VarBinary

['foo', 'bar', 'foo', 'bar', null, 'baz']

以字典編碼形式,這可能顯示為

data VarBinary (dictionary-encoded)
   index_type: Int32
   values: [0, 1, 0, 1, null, 2]

dictionary
   type: VarBinary
   values: ['foo', 'bar', 'baz']

請注意,字典允許包含重複值或空值

data VarBinary (dictionary-encoded)
   index_type: Int32
   values: [0, 1, 3, 1, 4, 2]

dictionary
   type: VarBinary
   values: ['foo', 'bar', 'baz', 'foo', null]

此類陣列的空值計數僅由其索引的有效性點陣圖決定,與字典中的任何空值無關。

由於在某些情況下(例如在 JVM 中),無號整數可能更難以使用,因此我們建議優先使用有號整數而不是無號整數來表示字典索引。此外,我們建議避免使用 64 位元無號整數索引,除非應用程式需要。

我們將在下文進一步討論與序列化相關的字典編碼。

行程結束編碼佈局#

版本 Arrow 新增:欄狀格式 1.3

行程結束編碼 (REE) 是執行長度編碼 (RLE) 的一種變體。這些編碼非常適合表示包含相同值序列(稱為行程)的資料。在行程結束編碼中,每個行程都表示為一個值和一個整數,該整數給出行程在陣列中結束的索引。

任何陣列都可以進行行程結束編碼。行程結束編碼陣列本身沒有緩衝區,但有兩個子陣列。第一個子陣列稱為行程結束陣列,包含 16 位元、32 位元或 64 位元有號整數。每個行程的實際值都保存在第二個子陣列中。為了確定欄位名稱和綱要的目的,這些子陣列分別被指定標準名稱 run_endsvalues

第一個子陣列中的值表示從第一個行程到目前行程的所有行程的累積長度,即目前行程結束的邏輯索引。這允許使用二元搜尋從邏輯索引進行相對有效的隨機存取。個別行程的長度可以透過減去兩個相鄰的值來確定。(與執行長度編碼相比,在執行長度編碼中,行程的長度直接表示,並且隨機存取的效率較低。)

注意

由於 run_ends 子陣列不能有空值,因此有理由考慮為什麼 run_ends 是子陣列,而不是像 可變大小列表佈局 的偏移量一樣只是一個緩衝區。已考慮過這種佈局,但最終決定使用子陣列。

子陣列允許我們將與父陣列關聯的「邏輯長度」(解碼長度)和與子陣列關聯的「物理長度」(行程結束的數量)分開。如果 run_ends 是父陣列中的緩衝區,則緩衝區的大小將與陣列的長度無關,這會造成混淆。

行程的長度必須至少為 1。這表示行程結束陣列中的值均為正數且嚴格遞增。行程結束不能為空值。

REE 父項沒有有效性點陣圖,並且其空值計數欄位應始終為 0。空值編碼為值為空的行程。

例如,您可能有以下資料

type: Float32
[1.0, 1.0, 1.0, 1.0, null, null, 2.0]

在行程結束編碼形式中,這可能顯示為

* Length: 7, Null count: 0
* Child Arrays:

  * run_ends (Int32):
    * Length: 3, Null count: 0 (Run Ends cannot be null)
    * Validity bitmap buffer: Not required (if it exists, it should be all 1s)
    * Values buffer

      | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-63           |
      |-------------|-------------|-------------|-----------------------|
      | 4           | 6           | 7           | unspecified (padding) |

  * values (Float32):
    * Length: 3, Null count: 1
    * Validity bitmap buffer:

      | Byte 0 (validity bitmap) | Bytes 1-63            |
      |--------------------------|-----------------------|
      | 00000101                 | 0 (padding)           |

    * Values buffer

      | Bytes 0-3   | Bytes 4-7   | Bytes 8-11  | Bytes 12-63           |
      |-------------|-------------|-------------|-----------------------|
      | 1.0         | unspecified | 2.0         | unspecified (padding) |

每個佈局的緩衝區列表#

為了避免歧義,我們提供了每個佈局的記憶體緩衝區的順序和類型列表。

緩衝區佈局#

佈局類型

緩衝區 0

緩衝區 1

緩衝區 2

可變參數緩衝區

原始

有效性

資料

可變二進位

有效性

偏移量

資料

可變二進位視圖

有效性

視圖

資料

列表

有效性

偏移量

列表視圖

有效性

偏移量

大小

固定大小列表

有效性

Struct

有效性

稀疏聯合

類型 ID

密集聯合

類型 ID

偏移量

Null

字典編碼

有效性

資料(索引)

行程結束編碼

序列化和行程間通訊 (IPC)#

欄狀格式中序列化資料的原始單位是「記錄批次」。從語義上講,記錄批次是陣列的有序集合,稱為其欄位,每個陣列的長度彼此相同,但資料類型可能不同。記錄批次的欄位名稱和類型共同構成批次的綱要

在本節中,我們定義了一個協議,用於將記錄批次序列化為二進位酬載串流,並從這些酬載重建記錄批次,而無需記憶體複製。

欄狀 IPC 協議利用這些類型二進位訊息的單向串流

  • 綱要

  • 記錄批次

  • 字典批次

我們指定了一種所謂的封裝 IPC 訊息格式,其中包括序列化的 Flatbuffer 類型以及可選的訊息主體。我們在描述如何序列化每個組成的 IPC 訊息類型之前,先定義此訊息格式。

封裝訊息格式#

對於簡單的串流和基於檔案的序列化,我們為行程間通訊定義了一種「封裝」訊息格式。此類訊息可以透過僅檢查訊息元數據來「反序列化」為記憶體內 Arrow 陣列物件,而無需複製或移動任何實際資料。

封裝的二進位訊息格式如下

  • 32 位元續傳指示符。值 0xFFFFFFFF 表示有效訊息。此組件在 0.15.0 版本中引入,部分原因是為了滿足 Flatbuffers 的 8 位元組對齊要求

  • 32 位元小端序長度前綴,指示元數據大小

  • 訊息元數據,使用 Message.fbs 中定義的 Message 類型

  • 填充位元組到 8 位元組邊界

  • 訊息主體,其長度必須是 8 位元組的倍數

示意圖如下

<continuation: 0xFFFFFFFF>
<metadata_size: int32>
<metadata_flatbuffer: bytes>
<padding>
<message body>

完整的序列化訊息必須是 8 位元組的倍數,以便訊息可以在串流之間重新定位。否則,元數據和訊息主體之間的填充量可能是不確定的。

metadata_size 包括 Message 的大小加上填充。metadata_flatbuffer 包含序列化的 Message Flatbuffer 值,其內部包含

  • 版本號碼

  • 特定的訊息值(SchemaRecordBatchDictionaryBatch 之一)

  • 訊息主體的大小

  • 用於任何應用程式提供的元數據的 custom_metadata 欄位

從輸入串流讀取時,通常會先剖析和驗證 Message 元數據以取得主體大小。然後可以讀取主體。

綱要訊息#

Flatbuffers 檔案 Schema.fbs 包含所有內建資料類型和 Schema 元數據類型的定義,該元數據類型表示給定記錄批次的綱要。綱要由欄位的有序序列組成,每個欄位都有名稱和類型。序列化的 Schema 不包含任何資料緩衝區,僅包含類型元數據。

Field Flatbuffers 類型包含單個陣列的元數據。這包括

  • 欄位的名稱

  • 欄位的資料類型

  • 欄位在語義上是否可為空值。雖然這對陣列的物理佈局沒有影響,但許多系統區分可為空值和不可為空值的欄位,我們希望允許它們保留此元數據,以實現忠實的綱要往返。

  • Field 值的集合,用於巢狀類型

  • dictionary 屬性,指示欄位是否為字典編碼。如果是,則會分配字典「ID」,以便將後續字典 IPC 訊息與適當的欄位進行匹配。

我們還額外提供綱要層級和欄位層級的 custom_metadata 屬性,允許系統插入自己的應用程式定義的元數據以自訂行為。

記錄批次訊息#

記錄批次訊息包含與綱要確定的物理記憶體佈局相對應的實際資料緩衝區。此訊息的元數據提供了每個緩衝區的位置和大小,允許使用指標運算重建陣列資料結構,因此無需記憶體複製。

記錄批次的序列化形式如下

  • data header,定義為 Message.fbs 中的 RecordBatch 類型。

  • body,一個平坦的記憶體緩衝區序列,端到端寫入,並進行適當的填充以確保至少 8 位元組對齊

資料標頭包含以下內容

  • 記錄批次中每個平坦化欄位的長度和空值計數

  • 記錄批次主體中每個組成的 Buffer 的記憶體偏移量和長度

欄位和緩衝區透過記錄批次中欄位的前序深度優先遍歷進行平坦化。例如,讓我們考慮以下綱要

col1: Struct<a: Int32, b: List<item: Int64>, c: Float64>
col2: Utf8

此綱要的平坦化版本為

FieldNode 0: Struct name='col1'
FieldNode 1: Int32 name='a'
FieldNode 2: List name='b'
FieldNode 3: Int64 name='item'
FieldNode 4: Float64 name='c'
FieldNode 5: Utf8 name='col2'

對於產生的緩衝區,我們將具有以下內容(請參閱上表)

buffer 0: field 0 validity
buffer 1: field 1 validity
buffer 2: field 1 values
buffer 3: field 2 validity
buffer 4: field 2 offsets
buffer 5: field 3 validity
buffer 6: field 3 values
buffer 7: field 4 validity
buffer 8: field 4 values
buffer 9: field 5 validity
buffer 10: field 5 offsets
buffer 11: field 5 data

Buffer Flatbuffers 值描述了一塊記憶體的位置和大小。通常,這些是相對於下面定義的 封裝訊息格式 來解釋的。

Buffersize 欄位不需要考慮填充位元組。由於此元數據可用於在程式庫之間傳達記憶體內指標地址,因此建議將 size 設定為實際記憶體大小,而不是填充後的大小。

變長緩衝區#

Arrow 版本新增:欄狀格式 1.4

某些類型(例如 Utf8View)使用可變數量的緩衝區表示。對於預先排序的平坦化邏輯綱要中的每個此類欄位,variadicBufferCounts 中都會有一個條目,指示目前記錄批次中屬於該欄位的變長緩衝區數量。

例如,考慮以下綱要

col1: Struct<a: Int32, b: BinaryView, c: Float64>
col2: Utf8View

此綱要有兩個具有變長緩衝區的欄位,因此 variadicBufferCounts 在每個記錄批次中將有兩個條目。對於此綱要的記錄批次,其中 variadicBufferCounts = [3, 2],平坦化的緩衝區將為

buffer 0:  col1    validity
buffer 1:  col1.a  validity
buffer 2:  col1.a  values
buffer 3:  col1.b  validity
buffer 4:  col1.b  views
buffer 5:  col1.b  data
buffer 6:  col1.b  data
buffer 7:  col1.b  data
buffer 8:  col1.c  validity
buffer 9:  col1.c  values
buffer 10: col2    validity
buffer 11: col2    views
buffer 12: col2    data
buffer 13: col2    data

壓縮#

記錄批次主體緩衝區的壓縮有三種不同的選項:緩衝區可以未壓縮,緩衝區可以使用 lz4 壓縮編解碼器進行壓縮,或者緩衝區可以使用 zstd 壓縮編解碼器進行壓縮。訊息主體的平坦序列中的緩衝區必須使用相同的編解碼器分別壓縮。壓縮緩衝區序列中的特定緩衝區可以保持未壓縮狀態(例如,如果壓縮這些特定緩衝區不會明顯減少它們的大小)。

使用的壓縮類型在 記錄批次訊息data header 的可選 compression 欄位中定義,預設為未壓縮。

注意

lz4 壓縮編解碼器表示 LZ4 框架格式,不應與 「原始」(也稱為「區塊」)格式混淆。

序列化形式中壓縮和未壓縮緩衝區之間的差異如下

  • 如果 記錄批次訊息 中的緩衝區是壓縮的

    • data header 包括記錄批次主體中每個壓縮緩衝區的長度和記憶體偏移量,以及壓縮類型

    • body 包括壓縮緩衝區的平坦序列,以及未壓縮緩衝區的長度,以 64 位元小端序有號整數形式儲存在序列中每個緩衝區的前 8 個位元組中。此未壓縮長度可以設定為 -1,表示該特定緩衝區保持未壓縮狀態。

  • 如果 記錄批次訊息 中的緩衝區是未壓縮的

    • data header 包括記錄批次主體中每個未壓縮緩衝區的長度和記憶體偏移量

    • body 包括未壓縮緩衝區的平坦序列。

注意

某些 Arrow 實作缺乏支援,無法使用上述編解碼器之一或兩者來生產和消費具有壓縮緩衝區的 IPC 資料。請參閱 實作狀態 以了解詳細資訊。

某些應用程式可能會在它們用於儲存或傳輸 Arrow IPC 資料的協議中應用壓縮。(例如,HTTP 伺服器可能會提供 gzip 壓縮的 Arrow IPC 串流。)已經在其儲存或傳輸協議中使用壓縮的應用程式應避免使用緩衝區壓縮。雙重壓縮通常會降低效能,並且不會大幅提高壓縮率。

位元組順序(位元組序)(Endianness)#

Arrow 格式預設為小端序。

序列化綱要元數據具有一個位元組序欄位,指示記錄批次的位元組序。通常,這是生成記錄批次的系統的位元組序。主要用例是在具有相同位元組序的系統之間交換記錄批次。最初,當嘗試讀取位元組序與底層系統不符的綱要時,我們將傳回錯誤。參考實作側重於小端序,並為其提供測試。最終,我們可能會提供透過位元組交換進行自動轉換。

IPC 串流格式#

我們為記錄批次提供了串流協議或「格式」。它以一系列封裝訊息的形式呈現,每個訊息都遵循上述格式。綱要首先出現在串流中,並且對於後續的所有記錄批次都相同。如果綱要中的任何欄位是字典編碼的,則將包含一個或多個 DictionaryBatch 訊息。DictionaryBatchRecordBatch 訊息可能會交錯,但在 RecordBatch 中使用任何字典鍵之前,應在 DictionaryBatch 中定義它。

<SCHEMA>
<DICTIONARY 0>
...
<DICTIONARY k - 1>
<RECORD BATCH 0>
...
<DICTIONARY x DELTA>
...
<DICTIONARY y DELTA>
...
<RECORD BATCH n - 1>
<EOS [optional]: 0xFFFFFFFF 0x00000000>

注意

當記錄批次包含完全為空的字典編碼陣列時,會發生字典批次和記錄批次交錯的邊緣情況。在這種情況下,編碼欄的字典可能會在第一個記錄批次之後出現。

當串流讀取器實作正在讀取串流時,在每個訊息之後,它可能會讀取接下來的 8 個位元組,以確定串流是否繼續以及後續訊息元數據的大小。讀取訊息 Flatbuffer 後,您可以讀取訊息主體。

串流寫入器可以透過寫入包含 4 位元組續傳指示符 (0xFFFFFFFF) 後跟 0 元數據長度 (0x00000000) 的 8 個位元組或關閉串流介面來發出串流結束 (EOS) 信號。我們建議將「.arrows」檔案副檔名用於串流格式,儘管在許多情況下,這些串流永遠不會儲存為檔案。

IPC 檔案格式#

我們定義了一種支援隨機存取的「檔案格式」,它是串流格式的擴展。檔案以魔術字串 ARROW1(加上填充)開始和結束。檔案中接下來的內容與串流格式相同。在檔案末尾,我們寫入一個尾部資訊,其中包含綱要的冗餘副本(它是串流格式的一部分)以及檔案中每個資料區塊的記憶體偏移量和大小。這使得可以隨機存取檔案中的任何記錄批次。請參閱 File.fbs 以了解檔案尾部資訊的精確詳細資訊。

示意圖如下

<magic number "ARROW1">
<empty padding bytes [to 8 byte boundary]>
<STREAMING FORMAT with EOS>
<FOOTER>
<FOOTER SIZE: int32>
<magic number "ARROW1">

在檔案格式中,不要求在 RecordBatch 中使用字典鍵之前,應在 DictionaryBatch 中定義它們,只要鍵在檔案中的某個位置定義即可。此外,每個字典 ID 最多只能有一個非 Delta 字典批次(即,不支援字典替換)。Delta 字典按照它們在檔案尾部資訊中出現的順序套用。我們建議將「.arrow」副檔名用於使用此格式建立的檔案。請注意,使用此格式建立的檔案有時稱為「Feather V2」或使用「.feather」副檔名,名稱和副檔名源自「Feather (V1)」,這是 Arrow 專案早期針對 Python (pandas) 和 R 的語言無關快速資料框架儲存的概念驗證。

字典訊息#

字典以記錄批次序列的形式寫入串流和檔案格式,每個記錄批次都有單一欄位。因此,記錄批次序列的完整語義綱要由綱要以及所有字典組成。字典類型在綱要中找到,因此有必要先讀取綱要以確定字典類型,以便可以正確解釋字典

table DictionaryBatch {
  id: long;
  data: RecordBatch;
  isDelta: boolean = false;
}

訊息元數據中的字典 id 可以在綱要中被引用一次或多次,因此字典甚至可以用於多個欄位。請參閱 字典編碼佈局 區段,以了解有關字典編碼資料語義的更多資訊。

字典 isDelta 旗標允許擴展現有字典以用於未來的記錄批次實體化。設定了 isDelta 的字典批次表示其向量應與任何先前具有相同 id 的批次的向量串連。在編碼單一欄的串流中,字串列表 ["A", "B", "C", "B", "D", "C", "E", "A"] 與 Delta 字典批次可以採用以下形式

<SCHEMA>
<DICTIONARY 0>
(0) "A"
(1) "B"
(2) "C"

<RECORD BATCH 0>
0
1
2
1

<DICTIONARY 0 DELTA>
(3) "D"
(4) "E"

<RECORD BATCH 1>
3
2
4
0
EOS

或者,如果 isDelta 設定為 false,則字典會替換相同 ID 的現有字典。使用與上述相同的範例,替代編碼可以是

<SCHEMA>
<DICTIONARY 0>
(0) "A"
(1) "B"
(2) "C"

<RECORD BATCH 0>
0
1
2
1

<DICTIONARY 0>
(0) "A"
(1) "C"
(2) "D"
(3) "E"

<RECORD BATCH 1>
2
1
3
0
EOS

自訂應用程式元數據#

我們在三個層級提供 custom_metadata 欄位,以提供一種機制,供開發人員在 Arrow 協議訊息中傳遞應用程式特定的元數據。這包括 FieldSchemaMessage

冒號符號 : 將用作命名空間分隔符。它可以在一個鍵中多次使用。

ARROW 模式是 custom_metadata 欄位中內部 Arrow 使用的保留命名空間。例如,ARROW:extension:name

擴展類型#

使用者定義的「擴展」類型可以透過在 Field 元數據結構中的 custom_metadata 中設定某些 KeyValue 對來定義。這些擴展鍵是

  • 'ARROW:extension:name' 用於識別自訂資料類型的字串名稱。我們建議您為擴展類型名稱使用「命名空間」樣式前綴,以最大程度地減少與同一應用程式中的多個 Arrow 讀取器和寫入器發生衝突的可能性。例如,使用 myorg.name_of_type 而不是簡單的 name_of_type

  • 'ARROW:extension:metadata' 用於重建自訂類型所需的 ExtensionType 的序列化表示形式

注意

arrow. 開頭的擴展名稱保留給標準擴展類型,它們不應用於第三方擴展類型。

此擴展元數據可以註解任何內建 Arrow 邏輯類型。例如,Arrow 指定了一個標準擴展類型,該類型將 UUID 表示為 FixedSizeBinary(16)。Arrow 實作不一定需要支援標準擴展,因此不支援此 UUID 類型的實作只會將其解釋為 FixedSizeBinary(16),並在後續的 Arrow 協議訊息中傳遞 custom_metadata

擴展類型可以使用或不使用 'ARROW:extension:metadata' 欄位。讓我們考慮一些擴展類型範例

  • uuid 表示為 FixedSizeBinary(16),具有空元數據

  • latitude-longitude 表示為 struct<latitude: double, longitude: double>,具有空元數據

  • tensor(多維陣列)儲存為 Binary 值,並具有序列化元數據,指示每個值的資料類型和形狀。這可以是類似 JSON 的 {'type': 'int8', 'shape': [4, 5]},用於 4x5 儲存格張量。

  • trading-time 表示為 Timestamp,具有序列化元數據,指示資料對應的市場交易日曆

另請參閱

標準擴充類型

實作指南#

執行引擎(或框架、或 UDF 執行器、或儲存引擎等)可以僅實作 Arrow 規範的子集和/或在以下約束條件下擴展它

實作規範的子集#

  • 如果僅生產(而不是消費)Arrow 向量:可以實作向量規範的任何子集和相應的元數據。

  • 如果消費和生產向量:有一個要支援的向量最小子集。生產向量的子集及其相應的元數據始終可以。消費向量應至少將不受支援的輸入向量轉換為受支援的子集(例如,Timestamp.millis 轉換為 timestamp.micros 或 int32 轉換為 int64)。

擴展性#

執行引擎實作者也可以在內部使用自己的向量擴展其記憶體表示形式,只要它們永遠不會公開即可。在將資料發送到期望 Arrow 資料的另一個系統之前,應將這些自訂向量轉換為 Arrow 規範中存在的類型。