Parquet實戰:一份初學者指南
透過實踐高效展示Parquet格式
今天,我們將透過一個互動練習來學習Parquet檔案的工作原理。我們的目標是在下載儘可能少資料的情況下,檢索大型Parquet檔案的模式。
我們將從Hugging Face遠端分析檔案,而無需在本地下載任何內容。
讓我們看看fineweb-edu資料集,它包含1.3T的教育網頁令牌,用於訓練大型語言模型。
這是一個**巨大**的資料集!我們還可以看到這個資料集中有許多按日期分割槽的parquet檔案。我們將使用`CC-MAIN-2013-20/train-00000-of-00014.parquet`檔案,該檔案可在此處找到。
這個檔案有**2.37 GB**,但我們想在不下載整個檔案的情況下提取元資料和模式。
Parquet概述
讓我們簡要了解一下Parquet檔案的通用格式。
- **行組(Row Groups)**:資料的水平分割槽,將行分組在一起。它們允許對大型資料集進行高效查詢和並行處理。
- **列塊(Column Chunks)**:每個行組中資料的垂直切片,包含特定列的值。這種列式儲存可實現高效壓縮和查詢效能。
*圖片來源:Clickhouse*
- **模式(Schema)**:描述Parquet檔案中列布局和型別的元資料。
- **魔術位元組(Magic Bytes)**:Parquet檔案開頭和結尾的一系列位元組,用於識別其為Parquet格式。`PAR1`表示Parquet。
請密切注意檔案的最後一部分,即頁尾元資料。這是我們檢索模式最重要的部分。
頁尾由三個組成部分。
- 檔案元資料(頁尾元資料大小訊息之前的n個位元組)
- 頁尾大小(頁尾魔術位元組之前的4個位元組)
- 魔術位元組(檔案末尾的4個位元組(`PAR1`))
魔術位元組非常巧妙。本質上,它們是快速識別檔案型別的標準。你可以把它們看作是檔案的簽名。這是一個非常好的列表,這裡列出了不同的魔術位元組和不同的檔案型別。
使用HEAD請求遠端獲取檔案大小
首先,我們向URL傳送一個HEAD請求。這應該會給我們一些關於檔案的元資料,而無需下載整個檔案。
import requests
url = "https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu/resolve/main/data/CC-MAIN-2013-20/train-00000-of-00014.parquet?download=true"
# Get file content length with HEAD request
head_response = requests.head(url, allow_redirects=True)
file_size = int(head_response.headers['Content-Length'])
HEAD請求只會返回響應頭,不包含任何實際內容。您應該會在下面看到響應頭。
頭 | 值 | 描述 |
---|---|---|
內容型別 | 二進位制/八位位元組流 | 指示檔案是二進位制檔案 |
內容長度 | 2369456837 |
檔案大小(位元組)(2.37 GB) |
接受範圍 | 位元組 | 檔案支援部分內容請求;可以請求特定位元組範圍 |
這說明了什麼?
我們可以使用HTTP請求中的`Range`頭來讀取特定位元組範圍。這使我們能夠只查詢頁尾的位元組範圍。
HTTP範圍請求
查詢檔案的特定位元組範圍
Range頭部的示例如下:
Range: bytes=0-100
這將讀取檔案的前100個位元組。
提取頁尾大小
現在,讓我們使用這個Range頭來讀取頁尾大小,這將為我們提供讀取頁尾元資料的正確位元組範圍。
要跟進,您需要安裝`requests`和`pyarrow`包。
head_response = requests.head(url, allow_redirects=True)
file_size = int(head_response.headers['Content-Length'])
print(f"File size: {file_size} bytes")
在這裡,我們檢索魔術位元組之前的4個位元組,這將給我們完整的頁尾大小(**m**)。
讀取整個頁尾
現在我們知道了所有變數:
- 檔案長度
- 頁尾長度
我們可以使用最後一個Range請求來讀取模式和元資料。我們將使用`pyarrow`從原始位元組中讀取模式和元資料。
footer_start = file_size - 8 - footer_size
footer_headers = {"Range": f"bytes={footer_start}-{file_size-1}"}
footer_response = requests.get(url, headers=footer_headers)
# use pyarrow to extract metadata from bytes buffer
footer_buffer = io.BytesIO(footer_response.content)
parquet_file = pq.ParquetFile(footer_buffer)
parquet_schema = parquet_file.schema
parquet_metadata = parquet_file.metadata
print(parquet_file.schema)
print (parquet_file.metadata)
這將輸出
<pyarrow._parquet.ParquetSchema object at 0x107f87c80>
required group field_id=-1 schema {
optional binary field_id=-1 text (String);
optional binary field_id=-1 id (String);
optional binary field_id=-1 dump (String);
optional binary field_id=-1 url (String);
optional binary field_id=-1 file_path (String);
optional binary field_id=-1 language (String);
optional double field_id=-1 language_score;
optional int64 field_id=-1 token_count;
optional double field_id=-1 score;
optional int64 field_id=-1 int_score;
}
<pyarrow._parquet.FileMetaData object at 0x107f79210>
created_by: parquet-cpp-arrow version 15.0.0
num_columns: 10
num_rows: 785906
num_row_groups: 786
format_version: 2.6
serialized_size: 3255315
現在您已經瞭解瞭如何遠端提取和獲取Parquet檔案中的資訊,您可以看到像DuckDB這樣的工具如何高效地查詢Parquet檔案。
Hugging Face甚至內建了Parquet元資料檢視器,由hyparquet提供支援,它採用了與上述非常相似的方法。
**有趣的事實:**您可以使用幾種不同的庫從Hugging Face Hub讀取資料集並掃描parquet檔案: