parquet是一个支持列式存储的文件格式,对于大数据量,多维来说,如果只查询某些具体维来说,有很好的效率。具体体现在自身列式存储,同一列的数据顺序存放,在取某一列数据时,不需要像行式存储那样把整个数据行都查询出来,大大减少了IO。
parquet的详解可以参考:
里面讲的还是挺详细的,parquet是怎么设计的,怎么存储数据和schema的,都有很好的讲解。
在我使用impala查询parquet数据时,遇到一个现象,具体为:
1,select dt, count(*) from table where dt between '20170701' and '20170731' group by dt ; dt是分区在cm管理台看到impala读取hdfs数据只有8M;
2, select dt, count(id) from table where dt between '20170701' and '20170731' group by dt ; 将count* 换成了某一具体column,这时候impala读取hdfs数据10.9G;
3,select dt, count(id),sum(balance) from table where dt between '20170701' and '20170731' group by dt; 添加了sum(balance), 这时候impala读取hdfs数据15.6G.
table 整体数据量在20G
从2,3语句可以看出,多了一个column,impala在查询时读取parquet文件数据量确实增大了,也说明了parquet在read时,只需要读取所需要的column,而不是像行式存储将所有column都读取出来,减少了IO,说明列式存储能够很好的进行column pruning。但是对于第一条语句count(*) 来说,却只有很少的8M数据,自己有点疑惑,为何会这么少? 首先count(*) 和 count(column) 在impala里语义有区别。
The notation COUNT(*) includes NULL values in the total.
The notation COUNT(column_name) only considers rows where the column contains a non-NULL value.
从语义上来说,count(column) 是需要查看count的column字段是否为null,不是null的才进行count计算,所以impala会读取id字段的数据,而count(*)是不在乎具体是否是null,而且读取的数据量如此之少,很有可能就是直接读取的元数据信息中的row_number. 那么它又是读取的哪里的元数据信息呢,impala catalog中的? 还是 parquet数据格式中footer里面的row numbers呢?
因为从cm中的确看到了impala从hdfs读取了数据,所以可以判断并非直接从impala元数据中获取,所以接下来自己准备亲自通过读取parquet文件中 filemetadata 来看下是否真的通过parquet文件中的元数据获得。
其中一些关键代码:
ParquetMetadata readFooter = ParquetFileReader.readFooter(conf, path, ParquetMetadataConverter.NO_FILTER);
System.out.println(readFooter.toString());
List blockMeta = readFooter.getBlocks();
for(BlockMetaData bl : blockMeta) {
System.out.println(bl.getRowCount());
}
这样就获取了parquet文件元数据信息中的row number。在获取具体某一dt (20170720)目录下parquet文件,然后通过将row number全部加起来,发现与select count(*) from table where dt = ‘20170720’ 数值一模一样,且通过查看cm后台,发现读取的HDFS数据量为300kb,之前读取一个月的数据量为8M,300*30~= 8M ,也基本吻合。
综上,当impala 进行count(*) 计算时,如果数据文件是parquet,impala直接读取parquet中的fileMetadata信息,从中取出row count,而不会进行具体的取column数值计算。