Parquet實戰:一份初學者指南

社群文章 釋出於2024年8月14日

透過實踐高效展示Parquet格式

今天,我們將透過一個互動練習來學習Parquet檔案的工作原理。我們的目標是在下載儘可能少資料的情況下,檢索大型Parquet檔案的模式。

我們將從Hugging Face遠端分析檔案,而無需在本地下載任何內容。

讓我們看看fineweb-edu資料集,它包含1.3T的教育網頁令牌,用於訓練大型語言模型。

image/png

這是一個**巨大**的資料集!我們還可以看到這個資料集中有許多按日期分割槽的parquet檔案。我們將使用`CC-MAIN-2013-20/train-00000-of-00014.parquet`檔案,該檔案可在此處找到。

這個檔案有**2.37 GB**,但我們想在不下載整個檔案的情況下提取元資料和模式。

Parquet概述

讓我們簡要了解一下Parquet檔案的通用格式。

  • **行組(Row Groups)**:資料的水平分割槽,將行分組在一起。它們允許對大型資料集進行高效查詢和並行處理。
  • **列塊(Column Chunks)**:每個行組中資料的垂直切片,包含特定列的值。這種列式儲存可實現高效壓縮和查詢效能。

image/png *圖片來源:Clickhouse*

  • **模式(Schema)**:描述Parquet檔案中列布局和型別的元資料。
  • **魔術位元組(Magic Bytes)**:Parquet檔案開頭和結尾的一系列位元組,用於識別其為Parquet格式。`PAR1`表示Parquet。

image/png

請密切注意檔案的最後一部分,即頁尾元資料。這是我們檢索模式最重要的部分。

頁尾由三個組成部分。

  • 檔案元資料(頁尾元資料大小訊息之前的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**)。

image/png

讀取整個頁尾

現在我們知道了所有變數:

  • 檔案長度
  • 頁尾長度

我們可以使用最後一個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提供支援,它採用了與上述非常相似的方法

image/png

**有趣的事實:**您可以使用幾種不同的庫從Hugging Face Hub讀取資料集並掃描parquet檔案:

社群

註冊登入以發表評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.