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文件: