Administrator
Published on 2024-02-04 / 3 Visits
0

Parquet文件到底有何独特之处?

一、Parquet文件的格式拆解

概念

Row Group

A logical horizontal partitioning of the data into rows. There is no physical structure that is guaranteed for a row group. A row group consists of a column chunk for each column in the dataset.

简单来说就是将一块数据横着切分为几块,每一块叫做一个Row Group。

Column Chunk

A chunk of the data for a particular column. They live in a particular row group and are guaranteed to be contiguous in the file.

简单来说就是在切分完行组之后,又竖着切分,其中每一列的数据所构成的分片就被称为Column Chunk,最后再把这些Column Chunk顺序地保存。

Page

Column chunks are divided up into pages. A page is conceptually an indivisible unit (in terms of compression and encoding). There can be multiple page types which are interleaved in a column chunk.

把Column Chunk再横着切分,每一块成为一个Page,每个Page的默认大小为1M。

💡 前两步切分还能理解,这次切分是为了什么?
猜想:可能是为了让数据读取的粒度足够小,便于单条数据或小批量数据的查询。因为Page是Parquet文件最小的读取单位,同时也是压缩的单位,如果没有Page这一级别,压缩就只能对整个Column Chunk进行压缩,而Column Chunk如果整个被压缩,就无法从中间读取数据,只能把Column Chunk整个读出来之后解压,才能读到其中的数据。

文件格式

Header的内容很少,只有4个字节,本质是一个magic number,用来指示文件类型。这个magic number目前有两种变体,分别是“PAR1”和“PARE”。其中“PAR1”代表的是普通的Parquet文件,“PARE”代表的是加密过的Parquet文件。

Data Block

实际存储的数据,按照第一节的方式存储。

Index

https://parquet.apache.org/docs/file-format/pageindex/#technical-approach
参考官方文档说明,后续迭代已经把这一部分索引独立出来和Row Group分开存储,靠近Footer。

目前Parquet的索引有两种,一种是Max-Min统计信息,一种是BloomFilter。其中Max-Min索引是对每个Page都记录它所含数据的最大值和最小值,这样某个Page是否不满足查询条件就可以通过这个Page的max和min值来判断。BloomFilter索引则是对Max-Min索引的补充,针对value比较稀疏,max-min范围比较大的列,用Max-Min索引的效果就不太好,BloomFilter可以克服这一点,同时也可以用于单条数据的查询。

Footer是Parquet元数据的大本营,包含了诸如schema,Block的offset和size,Column Chunk的offset和size等所有重要的元数据。另外Footer还承担了整个文件入口的职责,读取Parquet文件的第一步就是读取Footer信息,转换成元数据之后,再根据这些元数据跳转到对应的block和column,读取真正所要的数据。
关于Footer还有一个问题,就是为什么Parquet要把元数据放在文件的末尾而不是开头?这主要是为了让文件写入的操作可以在一趟(one pass)内完成。因为很多元数据的信息需要把文件基本写完以后才知道(例如总行数,各个Block的offset等),如果要写在文件开头,就必须seek回文件的初始位置,大部分文件系统并不支持这种写入操作(例如HDFS)。而如果写在文件末尾,那么整个写入过程就不需要任何回退。

二、Parquet是如何“知道”要跳过/扫描哪个行组的

很显然,在Footer中维护了Data部分的元数据,引擎可以根据每个Page的max和min值,选择是否要跳过这个Page,不用读取这部分数据,也就减少了IO的开销。

三、Parquet高效压缩的秘密

字典编码

假设有个字段name,在10条数据中的值分别为:

name:
	0, 1, 0, 2, 0, 2, 1, 3, 1, 0
dictionary:
	0 -> bruce, 1 -> cake, 2 -> kevin, 3 -> leo

这种方式在很多开源软件中都有使用,比如Elasticsearch,有两个优点:

  • 可以节省存储空间
  • 可以根据dictionary中的内容,过滤掉不符合条件的数据。在Parquet中,我们可以根据字符编码的特性来做相应的过滤。通过Column Metada中的信息,读取相应的Dictionary Page进行对比,从而过滤掉不符合条件的数据。
查询语句: SELECT name, school FROM student WHERE name = "leo"

File 0
	Row Group 0, Column 0 -> 0: bruce, 1:cake
	Row Group 1, Column 0 -> 0: bruce, 2:kevin
File 1
	Row Group 0, Column 0 -> 0: bruce, 1:cake, 2: kevin
	Row Group 1, Column 0 -> 0: bruce, 1:cake, 3: leo
	
通过对比过滤条件name = "leo",只需要加载File 1的Row Group 1中的数据。

基于位压缩的运行长度编码

https://link.zhihu.com/?target=https%3A//parquet.apache.org/docs/file-format/data-pages/encodings/%23a-namerlearun-length-encoding--bit-packing-hybrid-rle--3

场景

压缩算法使用场景
Dictionary Encoding小规模的数据集合,例如 IP 地址
Run Length Encoding重复数据
Delta Encoding有序数据集,例如 timestamp,自动生成的 ID,以及监控的各种 metrics
Prefix EncodingDelta Encoding for strings