Arrow 和 Parquet 第 1 部分:基本類型和可空性
已發布 2022 年 10 月 05 日
作者 tustvold 和 alamb
簡介
我們最近在 Rust Apache Arrow 中完成了一個長期專案,以完成對讀取和寫入任意巢狀 Parquet 和 Arrow 結構描述的支援。這是一個複雜的主題,而且我們發現缺乏平易近人的技術資訊,因此撰寫了這篇部落格文章,與社群分享我們的學習成果。
Apache Arrow 是一種開放、語言獨立的欄狀記憶體格式,適用於平面和階層式資料,為了高效的分析操作而組織。Apache Parquet 是一種開放、面向欄的資料檔案格式,專為非常高效的資料編碼和檢索而設計。
分析系統越來越常使用 Arrow 來處理儲存在 Parquet 檔案中的資料,因此它們之間快速、高效且正確的轉換是一個關鍵的建構區塊。
從歷史上看,分析處理主要集中於查詢具有表格結構描述的資料,其中有固定數量的欄,且每列包含每個欄的單一值。然而,隨著 XML、JSON 等結構化文件格式的日益普及,僅支援表格結構描述可能會讓使用者感到沮喪,因為這需要通常是非平凡的資料轉換才能首先展平文件資料。
截至 20.0.0 版本(於 2022 年 8 月發布),用於讀取結構化類型的 Rust Arrow 實作已具備完整功能。入門指南可以在這裡找到,並隨時在我們的 錯誤追蹤器 上提出任何問題。
在本系列文章中,我們將解釋 Parquet 和 Arrow 如何表示巢狀資料,重點介紹它們之間的異同之處,並讓您了解格式之間轉換的實務性。
欄狀與記錄導向
首先,有必要退後一步,討論欄狀和記錄導向資料格式之間的差異。在記錄導向資料格式(例如換行符分隔的 JSON (NDJSON))中,給定記錄的所有值都連續儲存。
例如
{"Column1": 1, "Column2": 2}
{"Column1": 3, "Column2": 4, "Column3": 5}
{"Column1": 5, "Column2": 4, "Column3": 5}
在欄狀表示法中,給定欄的資料而是連續儲存
Column1: [1, 3, 5]
Column2: [2, 4, 4]
Column3: [null, 5, 5]
除了可能產生更好的資料壓縮之外,欄狀佈局還可以顯著提高某些查詢的效能。這是因為在記憶體中連續佈置資料,可讓編譯器和 CPU 更好地利用並行處理的機會。SIMD 和 ILP 的細節遠超出本文的範圍,但重要的結論是,處理大量資料而沒有介入的條件分支具有顯著的效能優勢。
Parquet 與 Arrow
Parquet 和 Arrow 是互補的技術,它們在設計上做出了一些不同的權衡。特別是,Parquet 是一種為最大空間效率而設計的儲存格式,而 Arrow 是一種旨在由向量化計算核心操作的記憶體內格式。
主要的區別在於 Arrow 提供對任何陣列索引的 O(1)
隨機存取查找,而 Parquet 則不提供。特別是,Parquet 使用 dremel 記錄切碎、可變長度編碼方案 和 區塊壓縮 來大幅縮減資料大小,但這些技術會犧牲效能良好的隨機存取查找。
一種常見的模式可以發揮每種技術的優勢,即以 Arrow 格式從壓縮表示法(例如 Parquet)串流資料,以千列批次處理,個別處理這些批次,並將結果累積在更壓縮的表示法中。這受益於有效執行 Arrow 資料計算的能力,同時控制記憶體需求,並允許計算核心與來源和目的地的編碼無關。
Arrow 主要是一種記憶體內格式,而 Parquet 是一種儲存格式。
非可空基本類型欄
讓我們從最簡單的非可空 32 位元帶正負號整數列表案例開始。
在 Arrow 中,這將表示為 PrimitiveArray
,它會將它們連續儲存在記憶體中
┌─────┐
│ 1 │
├─────┤
│ 2 │
├─────┤
│ 3 │
├─────┤
│ 4 │
└─────┘
Values
Parquet 有多種 不同的編碼 可用於整數類型,其確切細節超出本文的範圍。廣義來說,資料將儲存在一個或多個 DataPage 中,其中包含編碼形式的整數
┌─────┐
│ 1 │
├─────┤
| 2 │
├─────┤
│ 3 │
├─────┤
│ 4 │
└─────┘
Values
可空基本類型欄
現在讓我們考慮可空欄的案例,其中某些值可能具有特殊的前哨值 NULL
,表示「此值未知」。
在 Arrow 中,Null 值與值分開儲存,以 有效性位元遮罩 的形式儲存,值緩衝區中對應位置包含任意資料。這種節省空間的編碼表示以下範例的整個有效性遮罩都使用 5 位元儲存
┌─────┐ ┌─────┐
│ 1 │ │ 1 │
├─────┤ ├─────┤
│ 0 │ │ ?? │
├─────┤ ├─────┤
│ 1 │ │ 3 │
├─────┤ ├─────┤
│ 1 │ │ 4 │
├─────┤ ├─────┤
│ 0 │ │ ?? │
└─────┘ └─────┘
Validity Values
在 Parquet 中,有效性資訊也與值分開儲存,但是,它不是編碼為有效性位元遮罩,而是編碼為稱為定義層級的 16 位元整數列表。與 Parquet 中的其他資料一樣,這些整數定義層級使用高效編碼儲存,並將在下一篇文章中詳細說明,但就目前而言,定義層級 1
表示有效值,而 0
表示 Null 值。與 Arrow 不同,Null 值未編碼在值列表中
┌─────┐ ┌─────┐
│ 1 │ │ 1 │
├─────┤ ├─────┤
│ 0 │ │ 3 │
├─────┤ ├─────┤
│ 1 │ │ 4 │
├─────┤ └─────┘
│ 1 │
├─────┤
│ 0 │
└─────┘
Definition Values
Levels
接下來:巢狀和階層式資料
在對 Arrow 和 Parquet 如何以不同方式儲存可空性/定義有了基礎理解之後,我們已準備好繼續討論更複雜的巢狀類型,您可以在我們的 關於此主題的下一篇部落格文章 中閱讀相關資訊。