Parquet 内容定义分块用于高效数据去重
Parquet 内容定义分块(CDC)现已在 PyArrow 和 Pandas 中可用,使得在类似 Hugging Face 的 Xet 存储层的内容可寻址存储系统上对 Parquet 文件进行高效去重成为可能。通过仅上传或下载已更改的数据块,CDC 大幅降低了大规模数据集的数据传输和存储成本。
使用 Xet 与 CDC 优化 Parquet 存储
Hugging Face 托管了超过 4 PB 的 Parquet 文件。为优化这些存储,Hugging Face 引入了 Xet 存储层,它使用内容定义分块来对数据块进行去重并提升传输速度。
虽然 Xet 与格式无关,但标准的 Apache Parquet 布局和列块压缩即使在数据仅有细微更改时,也常会产生完全不同的字节级表示。这导致去重效果不佳。Parquet CDC 通过在序列化或压缩之前,基于列的逻辑内容将其划分为数据页,从而确保列按内容分块,最大限度地减少相似数据集之间的字节级差异。
实现与使用
用户可以在 PyArrow 或 Pandas 中传入 use_content_defined_chunking=True 参数来启用 Parquet CDC:
PyArrow:
import pyarrow.parquet as pq
pq.write_table(table, "hf://datasets/{user}/{repo}/path.parquet", use_content_defined_chunking=True)
Pandas:
import pandas as pd
df.to_parquet("hf://datasets/{user}/{repo}/path.parquet", use_content_defined_chunking=True)
常见数据工作流中的性能表现
在配合 Xet 存储层使用时,Parquet CDC 在多种常见的数据工程场景中提供了显著的去重收益:
完全复制与列修改
- 完全复制: 上传相同的文件到不同的仓库是瞬时的,因为系统识别到内容已经存在。
- 添加/删除列: 只会传输新增的列和更新的页脚元数据。例如,向数据集添加新列时,仅为一个 96.6MB 的文件上传了 575kB 的新数据。
- 更改列类型: 对列进行类型转换(例如
int64转为int32)只需上传已修改的列和更新的元数据。
行级操作
- 追加行: 将新行拼接到表中只会传输新数据,因为原有行在存储层保持不变。
- 插入和删除行: 在普通 Parquet 中,插入或删除一行会导致所有后续行移动,改变所有后续数据页的字节级表示,进而必须完整重新上传。Parquet CDC 通过基于内容的分块避免了这种情况,使系统能够识别并仅传输具体的更改。在测试中,向数据集插入行将传输量从接近 90MB(普通)降低到 6MB(CDC)。
结构和分片变更
- 行组大小: 更改行组大小(例如从 1M 行改为 128k 或 256k)通常会在数据页之间移动值。即使出现这些结构性变化,Parquet CDC 仍能保持去重效率。
- 文件级拆分: 当数据集被拆分为不同数量的分片(例如 5、10 或 20 个分片)时,Parquet CDC 与 Xet 结合可确保整体上传大小几乎不超过原始数据集,无论分片边界如何。
限制与注意事项
由于 Parquet CDC 在数据页(列块)层面工作,其效果取决于更改的选择性。如果过滤或修改影响了数据集大多数数据页,则会有更多块被识别为唯一,从而导致去重率下降。
Sources
- OriginalParquet Content-Defined Chunking