参考文档
https://parquet.apache.org/documentation/latest/ : 官网
https://blog.csdn.net/Night_ZW/article/details/108359619
https://cnblogs.com/panpanwelcome/p/10248990.html
https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/36632.pdf : dermel论文
https://www.cnblogs.com/ulysses-you/p/7985240.html
zhuanlan.zhihu.com/p/111822325
学习parquet文件格式,先来了解列存储和行存储
- 行存储:一条记录存储在连续的磁盘上
- 列存储:一条记录存储在磁盘的不同位置,但是整个关系(表)的一列存储在连续的磁盘上.
了解oltp和olap的典型场景
- tp
- 单条记录的增删改查,通常是整条记录
- 频繁的插入或更新;
- ap
- 某几列的整表统计.分组,排序,聚合等
分析
- 单条记录的增删改查
- 行存储通过索引找到数据直接修改数据,但是列存储却需要将记录分成列,再对每列操作.操作数指数上升
- 统计
- 行存储需要整表扫描并计算
- 列存储
- 选择部分列:不需要所有的列都读出来,读数少
- 过滤不需要的数据
- 同列同类型:高压缩,每列都可以选择不同的压缩方式,数据量更少,缓存效果更高等
- 向量计算
- 行列混合存储
- 明面上是行存储,实际用列存储;
- parquet,orc
parquet
-
新型列式存储格式;与平台,语言无关;灵感来源Google Dremel论文;支持嵌套式存储格式;
-
-
基本架构如上图:java学习可以maven仓库搜parquet
-
<!-- https://mvnrepository.com/artifact/org.apache.parquet/parquet-avro --> <dependency> <groupId>org.apache.parquet</groupId> <artifactId>parquet-avro</artifactId> <version>1.12.2</version> </dependency>
glossary
结构图:
引用一位知乎大佬的图
元数据
- 文件元数据
- 主要存储schema等;源码当中有一个ParquetMetaData包含了(FileMetaData和BlockMetaData)
- 行组元数据
- 行数量,数据量大小,列块元数据等;
- 列块元数据
- offset,encoding等
- 统计信息:valueCount,totalSize,max,min,numNulls等
读写流程
- 从后往前读,读取magic Number,判断是否是一个parquet文件;读取Footer Length,读取元数据的大小,计算元数据的起始位置;读取元数据.
- 通过列块元数据过滤更多的数据(max,min)(predicate pushdown filter);
- 字典页:假设存储的数据类型是String,则对string编码,string1 -> 1,string2 -> 2;这样存储的数据就是1211222这样;再存储字段映射关系即可.
Striping/assembly算法
- schema树形结构图
parquet对嵌套格式的支持,源于dermel论文
基于上面的图
- message:相当于表
- 里面的字段分三种
- 重复数
- 三种:required(必须有的),optional(可以为null),repeated(重复的,可以有多条数据)
- 字段类型:普通类型和复杂类型(group);普通类型就是标准类型,复杂类型,就是表示嵌套结构,下一层还有字段;
- 字段名
- 说明:上图links.forward字段,第一条数据有3个forward;所以存储3条数据:repeated表示是可以重复的
- 个人误区:之前以为forward这样的list会存储到多个列中,但是实际只存一个列多行;
- 重复数
definition levels 和repetion levels
数据存储好后,如何读取数据将数据还原
存储中,除了存储了值还需要存储definition levels(d)和repetion levels®
- repetion levels:标识一个重复深度.0表示新记录;只记录repeated
- 以Name.Language.code为例
- 第一条数据起始是0,第二条数据重复Name,Language,所以是2,第三条重复Name,是1,第四条也是重复Name是1,第五条新数据0
- 以Name.Language.code为例
- definition levels:嵌套结构中,repeted会有多行数据,表示一个定义等级;required是必须定义的,所以是不需要的.
- Name.Url为例
- 前面两条都定义了Name和Url都是2.第三条定义了Name没有定义Url.所以是1
- 再以Name.Language.Country为例
- 第一条数据定义了Name.Language.Country所以是3,第二条定义了Name.Language是2,第三条定义Name是1,然后根据层级是3和1;
- Name.Language.code
- code的定义等级只有2,required不记录等级;可以对比country为3.
- Name.Url为例
我们该如何根据这些信息拼凑出真正的数据呢
1. 假设要取一条数据.通过0可以判断这是一条数据.
2. 如何将这一条数据根据schema还原成原来的样子
3. 读取DocId,赋值
读取Links.Backward:读第一条数据赋值,读第二条数据判断r=1,表示重复1(第一个repeated),即重复Backward,增加一条Backword;
读取Links.Forward:读取第一条,赋值
读取Name.Language.code:读取第一条赋值,第二条,r=2,即重复深度为Language;
读取Name.Language.Country:读取第二条 r=2,重复深度为Language
...
日常使用
1. 一般是作为hive的存储格式 create table t(id int) stored as parquet;
2. hive修改列名后读取的数据是null
parquet.column.index.access = true
可以在建表时加入以上参数;hive默认的读取parquet文件是按照名称读取的,所以按照名称自然是查不到的.因为parquet本身是不支持修改的.
这个参数的功能是使hive读取parquet文件时使用序列号读取.这样,更改了名字也能读
orc默认是按序列号读取的.
可以直接用spark-shell读取文件,会发现读取出来的是原字段名;
3. hive修改列类型后直接导致查询出错
presto和hive查询会报错,而spark不会
presto(trino)默认使用hive的元数据读取文件
设置参数:hive.parquet.use_column_names=true;使用文件schema读取文件,而不是hive MetaData
spark对parquet的文件做了特殊优化,spark读取parquet文件会按照文件的schema读取,而且当hive元数据和schema不一致时做优化.
hive:用原始数据重新覆盖表即可