Parquet 列式存储

Posted by Charlie Lin on November 24, 2020

Parquet 列式存储

Parquet格式是什么

  • 支持嵌套结构的列式存储格式
  • 适用于 OLAP 场景,按列存储和扫描

特点

  1. 更高的压缩比。因为每个列都具有相同的数据结构(列的同构性),可以选择使用对该列最适合的压缩算法。
  2. 更小的 IO 操作。使用映射下推与谓词下推,跳过不满足条件的列,能减少不必要的数据扫描与 IO 代价。

    映射下推,这是列式存储最突出的优势,是指在获取数据时只需要扫描需要的列,不用全部扫描。 谓词下推,是指通过将一些过滤条件尽可能的在最底层执行以减少结果集。谓词就是指这些过滤条件,即返回bool:true和false的表达式,比如SQL中的大于小于等于、Like、Is Null等。 同时还可以保存每一列的统计信息(min、max,avg 等等)

  3. 语言无关,适配多种组件与语言,能与 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 对象,如下图,有 r1r2 两条记录。 那么在列式存储里,它是如何被组织的呢?
这里需要引入两个维度:Repetition levelsDefinition levels

  1. 先来看 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 是重复的,LanguageName.Language.Code 里排第二,因而 'en'Repetition levels = 2;
    接下来的 NULL 「出现」在 r1 里的第二个 Name 里,它与第一个出现的 Name.Language.CodeName 字段这里就是重复的,因而它的 Repetition levels = 1;
    再接下来的 'en-gb' 也是同理,它出现在 r1 里,也是与第一个出现的 Name.Language.CodeName 字段这里就是重复的,因而它的 Repetition levels = 1;
    最后的 NULL 就不同了,他是出现在第二行记录 r2 里,因而它的 Repetition levels = 0。
  2. 再来看 Definition levels,它定义的是,在某个值的完整路径上,有多少个字段是可以undefined 的,却被 define 了的。换句话说,也就是在它的完整路径上,出现了多少个 optional 和 repeated 的字段。
    Name.Language.Country 为例。这个字段在两行记录里一个出现了 5 次,'us'NULLNULL'gb'NULL。我们也一个个的来看。
    首先是 'us',三个字段都可以不出现,因而 Definition levels = 3; 其次是 NULL,只有 NameLanguage 出现了,Country 本身没有出现,d = 2; 接下来的 NULL,只有 Name 出现了,d = 1; 再接着是 'gb',三个字段都出现了,d = 3; 最后是 NULL,只有 Name 出现,d = 1。

因而,在列式存储结构里的每一个值都可以用一个三元组(r, d, value)来表示。