介绍
Parquet是目前比较流行的大数据文件列存储格式,主流的大数据计算框架都对其有良好的支持,包括spark,hive,impala等。相对于传统的行存储模式,列存储主要有以下优点:
可以跳过不符合条件的数据,只读取需要的数据,降低IO数据量。
压缩编码可以降低磁盘存储空间。由于同一列的数据类型是一样的,可以使用更高效的压缩编码(例如Run Length Encoding和Delta Encoding)进一步节约存储空间。
只读取需要的列,支持向量运算,能够获取更好的扫描性能。
相对于其它的列式存储格式,例如ORC,Parquet主要优势在于支持更为广泛,且对嵌套数据的支持更好。详细的比较可以参考
http://dongxicheng.org/mapreduce-nextgen/columnar-storage-parquet-and-orc/
主要概念
- Parquet文件是以二进制方式存储的,不能直接读取和修改,文件中包括该文件的数据和schema(自解析,可以理解为类似JSON)
- 行组(Row Group):按照行将数据物理上划分为多个单元,每一个行组包含一定的行数,Row group是数据读写时候的缓存单元,Parquet读写的时候会将整个行组缓存在内存中;
- 列块(Column Chunk):列块负责存储一个行组中某一列的数据,行组中的所有列连续的存储在这个行组文件中。不同的列块可能使用不同的算法进行压缩。
- 页(Page):每一个列块又划分为多个页,Page是压缩和编码的最小单元,对数据模型来说是透明的,在同一个列块的不同页可能使用不同的编码方式。
性能调优
如果采用HDFS文件系统,影响Parquet文件读写性能的参数主要有两个,dfs.blocksize和parquet.block.size
- dfs.blocksize: 控制HDFS file中每个block的大小,该参数主要影响计算任务的并行度,例如在spark中,一个map操作的默认分区数=(输入文件的大小/dfs.block.size)*输入的文件数(分区数等于该操作产生的任务数),如果dfs.block.size设置过大或过小,都会导致生成的Task数量不合理,因此应根据实际计算所涉及的输入文件大小以及executor数量决定何时的值。
- parquet.block.size 控制parquet的Row Group大小,一般情况下较大的值可以组织更大的连续存储的Column Chunk,有利于提升I/O性能,但上面也提到Row group是数据读写时候的缓存单元,每个需要读写的parquet文件都需要在内存中占据Row Group size设置的内存空间(读取的情况,由于可能跳过部分列,占据的内存会小于Row Group size),这样更大的Row Group size意味着更多的内存开销。同时设置该值时还需要考虑dfs.blocksize的值,尽量让Row Group size等同于HDFS一个block的大小,因为单个Row Group必须在一个计算任务中被处理,如果一个Row Group跨越了多个hdfs block可能会导致额外的远程数据读取。一般推荐的参数一个Row group大小1G,一个HDFS块大小1G,一个HDFS文件只含有一个块。
在Spark中可以使用如下方式修改默认配置参数:
val ONE_GB = 1024 * 1024 * 1024
sc.hadoopConfiguration.setInt("dfs.blocksize", ONE_GB)
sc.hadoopConfiguration.setInt("parquet.block.size", ONE_GB)
参考资料
http://ingest.tips/2015/01/31/parquet-row-group-size
http://www.infoq.com/cn/articles/in-depth-analysis-of-parquet-column-storage-format