在Impala 4.0源码解析之BROADCAST/SHUFFLE代价计算这篇文章中我们提到,Impala在对BROADCAST/SHUFFLE进行代价计算的时候,需要用到表的统计信息。关于Impala的统计信息,网上也有一些资料介绍,但是大多不全。本文将结合官方文档,从内容、计算等各方面尽可能详细地介绍下Impala统计信息的相关知识。
目前来看,Impala的统计信息主要分为两种:表级别和列级别的。我们分别来看下这两种统计信息有何不同。
当我们执行SHOW TABLE STATS 的时候,Impala会返回这个表的相关统计信息,这里我们以Impala自带的functional_parquet.alltypes测试表为例,如下所示:
我们结合上述截图,分别看下每列的具体含义:
对于分区表而言,执行SHOW PARTITIONS 结果与上述一样。但是如果是非分区表,则会直接报错。需要注意,这里是以hdfs表为例,如果对kudu表执行SHOW PARTITIONS,结果则不一样,如下所示:
如果表没有计算统计信息,那么结果就是这样的:
可以看到,Rows这一列都是-1,但是Files和Size仍然是有数据的。
上面我们介绍了表级别的统计信息,现在我们来看下列级别的统计信息是什么样的。当我们执行SHOW COLUMN STATS 的时候,Impala会返回这个表的各个列的统计信息,这里我们以tpch.customer为例,如下所示:
这里一共有8列值,我们分别介绍下每一列的含义:
如果说表没有统计信息的话,我们执行上述的查询,结果如下所示:
可以看到,除了定长类型的Max Size和Avg Size,其他的值都是-1,表示不存在。
上面我们分别介绍了表级别和列级别的统计信息,现在我们来看一下Impala是如何计算统计信息的。目前在Impala中主要就是通过COMPUTE STATS 语句来进行统计信息计算,主要分为以下几种情况:
更多关于COMPUTE STATS的用法,可以参考:COMPUTE STATS Statement。其实,Impala在进行统计信息计算的时候,就是提交了几条SQL来获取相应的信息,然后存储到hms中,我们以COMPUTE INCREMENTAL STATS alltypes PARTITION(year=2009,month=1) 为例,此时Impala会自动提交两条SQL,如下所示:
其中,第一条SQL就是按照分区进行分组count计算,用于统计每个分区的记录数;第二条SQL就是计算每一列的distinct、nulls等值。对于上述不同的情况,SQL也会有所调整,例如对于采样计算,会使用SAMPLED_NDV,对于指定的列,只会选择这些列进行计算等等。
还有一点需要注意,不要交替使用COMPUTE STATS 和COMPUTE INCREMENTAL STATS,当我们要在两种计算方式之间进行切换的时候,先执行DROP STATS 操作来删除之前的统计信息,然后再切换到另外一种计算方式。当然,对于非分区表,如果使用了COMPUTE INCREMENTAL STATS,最终Impala也会自动替换成COMPUTE STATS的计算方式。
接下来,我们再简单看下,当执行SHOW TABLE/COLUMN STATS时,我们是如何获取到统计信息的。当表第一次访问被加载的时候,catalogd会从hms获取相应的统计信息,然后缓存在内存当中,相关函数调用关系如下所示:
load(HdfsTable.java):1179
-loadAllColumnStats(Table.java):413
--getTableColumnStatistics(MetastoreShim.java)
-loadAllColumnStats(Table.java):419
--injectColumnStats(FeCatalogUtils.java):165
---updateStats(Column.java):72
----update(ColumnStats.java)
通过函数调用可以看到,Impala会去加载表的列统计信息。此时,catalogd中就已经缓存了表的元数据,同时会通过statestored广播到各个coordinator节点,每个coordinator节点的jvm中也会保存一份元数据信息。当我们执行SHOW TABLE/COLUMN STATS 的时候,就会根据对应的SQL,返回相应的信息,相关的函数调用如下所示:
getStats(JniFrontend.java):387
-getColumnStats(Frontend.java):1209
--doGetColumnStats(Frontend.java)
getStats(JniFrontend.java):391
-doGetTableStats(Frontend.java):1275
--doGetTableStats(Frontend.java)
FE在进行SQL解析的时候会判断是哪种类型的SHOW STATEMENT,根据不同的操作类型,调用不同的函数。目前主要支持如下的四种操作:
//common/thrift/Frontend.thrift
enum TShowStatsOp {
TABLE_STATS = 0
COLUMN_STATS = 1
PARTITIONS = 2
RANGE_PARTITIONS = 3
}
可以看到,除了TABLE和COLUMN操作之外,还支持PARTITIONS操作,RANGE PARTITIONS则是针对Kudu表。
在Impala提供的web页面,我们可以查看text plan标签页,来判断表的统计信息是否完整,如下所示:
如果SQL中的多个表,都存在统计信息缺失的情况,也都会在这个warning里面一一显示。除了这个地方,我们查看对应表的SCAN HDFS节点执行计划,也会有对应的提示:
可以看到,rows、size等都是处于unavailable的状态,表示这些信息目前都是无法获取的。Impala的web页面提供了非常丰富的信息,可以为SQL诊断分析提供很好的依据。
到这里,关于Impala统计信息的介绍就差不多了。总结一下,本文主要介绍了Impala的表级别、列级别的统计信息内容,以及几种不同场景下的统计信息计算,最后简单介绍了统计信息是如何获取的。总的来说,统计信息可以帮助Impala生成更优的执行计划,对于减少内存消耗也有一定的帮助,是非常重要的信息,对于其他的计算引擎也是一样。所以建议尽量保证对于一些大表、核心表,进行周期性地统计信息计算,以提高查询性能。
值得一提的是,Impala在3.x版本中增加了一个query option:disable_hdfs_num_rows_estimate,默认为false,表示允许hdfs表,在统计信息缺失的情况下进行行数的预估,主要就是根据列的avg size进行预估计算。相关的计算逻辑位于HdfsScanNode.getStatsNumRows()函数中,感兴趣的同学可以自行阅读源码。