使用Hive union remove优化器的避坑指南
最近在测试Hive的时候,使用Hive的Union remove优化器遇到了一个奇怪的问题,记录下定位的过程和结果,避免其他同学也碰到同样的坑。
复现方法
使用版本:MRS_1.9.3 (Hive-2.3.3),MRS_2.1.0(Hive-3.1.0)
步骤:
create table test_union_all(version string, rk int); insert into test_union_all values('5.21.01',1),('5.21.00',2),('5.20.01',3); set hive.optimize.union.remove=true; --开启Union remove优化器 select version from (select version from test_union_all where rk <=3 ) t group by version union all select 'all' as version;
执行结果:
查询sql执行完毕后没有任何结果,怀疑hive.optimize.union.remove的影响,然后关闭了Union remove优化set hive.optimize.union.remove=false后执行结果:
可以查询到正确的数据。
之后对不同的执行引擎做了测试
执行引擎 | 使用union remove查询结果 | 不使用该优化后查询结果 |
MapReduce | 结果有误 | 结果准确 |
Tez | 结果准确 | 结果准确 |
Spark | 结果准确 | 结果准确 |
优化器原理
Union remove优化器是在Hive 0.10(HIVE-3276)引入,主要原理是减少对Union all子查询中间结果的二次读写,简单用图描述如下下:
开源相关介绍https://cwiki.apache.org/confluence/display/Hive/Union+Optimization
此优化器与编译时数据倾斜优化(HIVE-3086)同时使用效果较好,编译时倾斜优化可以针对倾斜的key单独做MapJoin,与其他key的正常Join进行union成最终结果。使用Union remove后可以去掉后面的union步骤,减少一次中间结果读写。
问题分析
回到之前的问题,从问题复现的情况看,只有MapReduce在使用的时候查询会有问题,
1) 先从开源上翻了有没有类似的问题,开源有个类似的jira HIVE-12788,不过仔细一看主要是改的hive.compute.query.using.stats后的统计信息优化器StatsOptimizator,而且对照代码,在当前的hive-2.3.3上已经合入了,排除HIVE-12788。
2) 对比了开关Union remove优化器后执行计划,发现开启Union remove后Stage的依赖貌似有问题,如下图
Stage-1是表的TableScan和GroupByOperator,Stage-2是常量”all”的TableScan,Stage-0是最后的Fetch Operator,去除union后Stage-0应该同时依赖Stage-1和Stage-2才对。
后来从开源上找到相关的jiar HIVE-20570在Hive 4.0.0上解决了。
通过合入开源的jira单并替换环境的jar包,解决了执行计划的问题,但是问题依然没有解决Orz..
合入HIVE-20570后效果
3) 问题到这里后就没有啥思路,中间也试过DEBUG Hive执行计划的过程,以及检查MapReduce任务输出的中间结果,都没有发现哪里问题。后来没办法去查看了Hadoop内核代码,发现MAPREDUCE-1501支持遍历子目录jira中是引入了一个参数mapred.input.dir.recursive,默认为false。是不是Hive默认没有开启此开关?
之后验证了下
set hive.optimize.union.remove=true; set mapred.input.dir.recursive=true; --Deprecation,最新参数为mapreduce.input.fileinputformat.input.dir.recursive select version from (select version from test_union_all2 where rk <=3 ) t group by version union all select 'all' as version;
查看结果,果然对了!
此问题到这里就已经解决了,Union remove优化后MapReduce任务数据写入最终目录的子目录,在HDFS默认不开启遍历子目录开关的情况下,Hive无法查询到数据。所以要切记,使用hive.optimize.union.remove优化的时候必须设置mapred.input.dir.recursive=true。
后续
上面分析完毕后,问题是解决了,但是有同学又有疑问了,为什么不设置mapred.input.dir.recursive=true的情况下,MapReduce引擎执行的结果有问题,而Tez和Spark都能正常返回结果。所以下面我们就看下这两个引擎的情况。
1) Tez支持union是在HIVE-6362中引入,对比了Tez开启和关闭hive.optimize.union.remove的执行计划,确认了设置hive.optimize.union.remove=true后Union remove优化是生效的。
之后检查了Tez编译执行计划的过程的代码,发现使用Tez引擎编译的时候,默认就已经将mapred.input.dir.recursive设置成了true(果然是亲生的0.0)
TezCompiler#init public void init(QueryState queryState, LogHelper console, Hive db) { super.init(queryState, console, db); // Tez requires us to use RPC for the query plan HiveConf.setBoolVar(conf, ConfVars.HIVE_RPC_QUERY_PLAN, true); // We require the use of recursive input dirs for union processing conf.setBoolean("mapred.input.dir.recursive", true); }
所以在使用Tez的时候,只需要设置hive.optimize.union.remove=true就能正常使用Union remove优化
2) 当Hive的执行引擎是spark时,比较了开关优化前后的执行计划,前后的执行计划完全一样,所以猜测这个优化是否在spark下没有效果,之后检查了Hive的代码,看到这个优化是排除了spark:
UnionProcessor# transform // Walk the tree again to see if the union can be removed completely HiveConf conf = pCtx.getConf(); opRules.clear(); if (conf.getBoolVar(HiveConf.ConfVars.HIVE_OPTIMIZE_UNION_REMOVE) && !conf.getVar(HiveConf.ConfVars.HIVE_EXECUTION_ENGINE).equals("spark")) { opRules.put(new RuleRegExp("R5", UnionOperator.getOperatorName() + "%" + ".*" + FileSinkOperator.getOperatorName() + "%"), UnionProcFactory.getUnionNoProcessFile());
Github上的提交记录https://github.com/apache/hive/commit/a7693b6b6c953414e86bb2f968340eb28e6b8b6f
所以在使用Spark的时候,不管是否设置hive.optimize.union.remove=true,都是没有此优化。
结论
Hive2/Hive3在使用MapReduce引擎的时候使用union remove优化器必须要同时设置hive.optimize.union.remove和mapred.input.dir.recursive,使用Tez引擎时只需要设置hive.optimize.union.remove;Spark引擎不支持此优化。
Tips:
1)Hive2.0.0版本之前开启遍历子目录还有另外一个开关hive.mapred.supports.subdirectories,这个开关在HIVE-11582中清理了,之后就直接使用MapReduce上的参数mapred.input.dir.recursive来控制了。
2)开源在Hive 4.x上默认把MapReduce上也开启了mapred.input.dir.recursive(HIVE-12812)
- 点赞
- 收藏
- 关注作者
评论(0)