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

社区

注册登录以发表评论