Parquet 列式存储
Parquet格式是什么
- 支持嵌套结构的列式存储格式
- 适用于 OLAP 场景,按列存储和扫描
特点
- 更高的压缩比。因为每个列都具有相同的数据结构(列的同构性),可以选择使用对该列最适合的压缩算法。
- 更小的 IO 操作。使用映射下推与谓词下推,跳过不满足条件的列,能减少不必要的数据扫描与 IO 代价。
映射下推,这是列式存储最突出的优势,是指在获取数据时只需要扫描需要的列,不用全部扫描。 谓词下推,是指通过将一些过滤条件尽可能的在最底层执行以减少结果集。谓词就是指这些过滤条件,即返回bool:true和false的表达式,比如SQL中的大于小于等于、Like、Is Null等。 同时还可以保存每一列的统计信息(min、max,avg 等等)
- 语言无关,适配多种组件与语言,能与 Parquet 配合的组件有:
查询引擎:Hive,Impala,Pig,Presto
计算框架:MapReduce,Spark,Cascading
数据模型:Avro,Thrift,Protocol Buffers,POJOs
列式存储
如何定义的
一个 Parquet 对象的定义,可以用如下格式:
message Document {
required int64 DocId;
optional group Links {
repeated int64 Backward;
repeated int64 Forward;
}
repeated group Name {
repeated group Language {
required string Code;
optional string Country;
}
optional string Url;
}
}
定义了一个 Document 的对象,包含必要字段 DocId
,可选的(出现 0 或 1 次)嵌套结构 Links
,重复的(出现 0 或 n 次)嵌套结构 Name
。
如何表示的
还是上面那个定义的 Document 对象,如下图,有 r1
,r2
两条记录。
那么在列式存储里,它是如何被组织的呢?
这里需要引入两个维度:Repetition levels
与 Definition levels
。
- 先来看
Repetition levels
,它定义的是某个字段是在某行的哪一个嵌套级别是重复的。
以Name.Language.Code
为例。在这个例子的两行记录里,Name.Language.Code
一个出现了五次,分别是'en-us'
,'en'
,NULL
,'en-gb'
,NULL
。
一个个来看,'en-us'
在r1
里是第一个出现的Name.Language.Code
,因而用 0 表示(特殊规定);
'en'
出现在跟'en-us'
同一个Name
嵌套里,也就是它们的Language
是重复的,Language
在Name.Language.Code
里排第二,因而'en'
的Repetition levels
= 2;
接下来的NULL
「出现」在r1
里的第二个Name
里,它与第一个出现的Name.Language.Code
在Name
字段这里就是重复的,因而它的Repetition levels
= 1;
再接下来的'en-gb'
也是同理,它出现在r1
里,也是与第一个出现的Name.Language.Code
在Name
字段这里就是重复的,因而它的Repetition levels
= 1;
最后的NULL
就不同了,他是出现在第二行记录r2
里,因而它的Repetition levels
= 0。 - 再来看
Definition levels
,它定义的是,在某个值的完整路径上,有多少个字段是可以undefined 的,却被 define 了的。换句话说,也就是在它的完整路径上,出现了多少个 optional 和 repeated 的字段。
以Name.Language.Country
为例。这个字段在两行记录里一个出现了 5 次,'us'
,NULL
,NULL
,'gb'
,NULL
。我们也一个个的来看。
首先是'us'
,三个字段都可以不出现,因而Definition levels
= 3; 其次是NULL
,只有Name
和Language
出现了,Country
本身没有出现,d = 2; 接下来的NULL
,只有Name
出现了,d = 1; 再接着是'gb'
,三个字段都出现了,d = 3; 最后是NULL
,只有Name
出现,d = 1。
因而,在列式存储结构里的每一个值都可以用一个三元组(r, d, value)来表示。