1 优化说明
在Hive中没有优化过的分组聚合:通过MR任务实现。Map端负责读数据,按分区字段分区,通过Shuffle,将数据发往Reduce端,各组数据在Reduce端完成最终的聚合运算。
Hive分组聚合优化主要针对减少Shuffle的数据量,使用map-site聚合,在map端维护一个hash table,对数据进行聚合,聚合结果按分组字段分区发送到reduce端。
map-side 聚合相关参数:
--启用map-side聚合,默认为true
set hive.map.aggr=true;
--用于检测源表数据是否适合进行map-side聚合。检测的方法是:
--先对若干条数据进行map-side聚合,
--若聚合后的条数和聚合前的条数比值小于该值,则认为该表适合进行map-side聚合;
--否则,认为该表数据不适合进行map-side聚合,后续数据便不再进行map-side聚合。
set hive.map.aggr.hash.min.reduction=0.5;
--用于检测源表是否适合map-side聚合的条数。
set hive.groupby.mapaggr.checkinterval=100000;
--map-side聚合所用的hash table,占用map task堆内存的最大比例,
--若超出该值,则会对hash table进行一次flush。
sethive.map.aggr.hash.force.flush.memory.threshold=0.9;
2 优化案例
优化前
先设置map-site聚合为关闭状态,再执行SQL:
set hive.map.aggr=false;
select
product_id,
count(*)
from order_detail
group by product_id;
未经过优化的分组聚合执行计划如下:
stage1:先扫描order_detail表->select product_id的字段->数据读入之后直接写入ruduce,没有经过map->在reduce端直接group by聚合->将数据发送到HDFS中。
在Yarn监控页面可以看到执行的时间。
优化
设置参数为true,再执行SQL。
set hive.map.aggr=true;
select
product_id,
count(*)
from order_detail
group by product_id;
优化后
执行计划:
stage1:先扫描order_detail表->select product_id的字段->使用hash table进行分组集合->数据读入之后直接写入ruduce,没有经过map->在reduce端对每一个Mapper发送来的数据进行group by合并->将数据发送到HDFS中。
查看执行时间,发现优化之后效果不明显。
原因:
可能在Map端进行分组聚合没有执行到需要的结果,检测表数据时可能不适合进行map-site聚合。
方法:
查看yarn中map的日志syslog,可以看到Map-Reduce的统计信息,Map的输入输出数据并没有发生大变化,可以说明在Map底层中,分组聚合并没有达到想要的效果,即在检验数据时认为该数据不适合做分组聚合。
Hive认为不适合做分组聚合的原因:
原表有2000W条数据,聚合到100W条,即聚合后的条数比聚合前的条数比值为1/20<0.5,按理说是可以做map-site聚合的。因为HIVE在校验时只对切片的表的开头的一段数据进行判断,当开头的一段数据校验后显示唯一的就不再对该切片的数据进行聚合。
强制实行分组聚合的方法:
因为比值为比值为1/20<0.5,实际上该数据是适合做分组聚合的。
将检测表数据的参数调整为1,再重新执行SQL。
set hive.map.aggr.hash.min.reduction=1;
查看yarn的Map端执行日志,观察数据量的变化,输出数据有明显减少。但聚合后Map端输出的行数是大于100W的。
原因:
当hash table占用map task堆内存已经超过最大比例,触发了Flush。在Flush前已经有某些ID的数据了,该数据已经输出了,flush之后还会重新进行hash计算,还有可能出现一些ID为已经输出的数据。也即一个ID的数据可能会输出多遍。
发生Flush的次数多了,聚合效果就不太好,继续优化方法:
把flush的阈值调大些,如果效果还是不明显则说明总内存小,可以调大总内存。
--map-side聚合所用的hash table,占用map task堆内存的最大比例,
--若超出该值,则会对hash table进行一次flush。
sethive.map.aggr.hash.force.flush.memory.threshold=0.9;
--进一步优化
set mapreduce.map.memory.mb=2048;