在执行一个类似以下Hive SQL的时候,遇到一个报错,语句和报错信息如下:
select
h.ID_1
,h.ID_2
,h.ID_3
,h.ID_4
,h.ID_5
,h.ID_6
,h.ID_7
,h.ID_8
,h.ID_9
,h.change_code
,h.s_date
,'2018-05-07' as e_date
from (
select
ID_1
,ID_2
,ID_3
,ID_4
,ID_5
,ID_6
,ID_7
,ID_8
,ID_9
,change_code
,s_date
from test_his where dt='2018-05-06'
) h
left join
(
select
ID_1
,ID_2
,ID_3
,ID_4
,ID_5
,ID_6
,ID_7
,ID_8
,ID_9
from s_test where dt='2018-05-07'
) s
on h.id_1=s.id_1
where
h.change_code='1';
报错如下:
Query ID = dw_20180519141227_8574d58f-01db-4ecc-a1f7-ad5d9d0135eb
Total jobs = 1
Execution log at: /tmp/dw/dw_20180519141227_8574d58f-01db-4ecc-a1f7-ad5d9d0135eb.log
2018-05-19 14:12:31 Starting to launch **local task to process map join**; maximum memory = 1046478848
2018-05-19 14:12:33 Processing rows: 200000 Hashtable size: 199999 Memory usage: 42522416 percentage: 0.041
2018-05-19 14:12:33 Processing rows: 300000 Hashtable size: 299999 Memory usage: 53507040 percentage: 0.051
2018-05-19 14:12:33 Processing rows: 400000 Hashtable size: 399999 Memory usage: 64643144 percentage: 0.062
...
...
...
2018-05-19 14:12:47 Processing rows: 6800000 Hashtable size: 6799999 Memory usage: 929123264 percentage: 0.888
2018-05-19 14:12:47 Processing rows: 6900000 Hashtable size: 6899999 Memory usage: 948926952 percentage: 0.907
Execution failed with exit status: 3
Obtaining error information
Task failed!
Task ID:
Stage-4
Logs:
/tmp/dw/hive.log
FAILED: Execution Error, return code 3 from org.apache.hadoop.hive.ql.exec.mr.MapredLocalTask
这个语句大体是test_his 表和s_test表进行left join,其中test_his表数据量很小,只有几行数据,s_test表有对应分区文件大约200M。
第一个问题是:看日志可以看出,是执行的MapJoin,但是应该是当只有参与关联的数据量比较小时才启用MapJoin,这个语句中,右表数据200M,是比较大的,怎么启用了MapJoin了呢?
第二个问题是:看日志“Starting to launch local task to process”,为什么是local task呢?难道是hive 语句没有到Yarn上去执行MapReduce任务还是在本地模式下执行的?
查询参数set hive.auto.convert.join,结果为true,意味着hive会根据输入数据文件的大小判断是否将普通的reduce join转换为mapjoin。
查询参set hive.mapjoin.smalltable.filesize,结果为25000000,意味着参与join的小表的数据量小于25M就会启用mapjoin,但是当前这个例子中,右表的数据量是200M,明显大于25M,怎么就启用了mapjoin了呢?接着向下找原因。
还有一个和mapjoin相关的参数,set hive.auto.convert.join.noconditionaltask,结果为true,那么意思就是:hive会根据join方的表的大小判断是否启用mapjoin,如果有N个表进行join,hive会判断其中n-1个表参与join的数据文件大小总和是否小于hive.auto.convert.join.noconditionaltask.size的值,如果小于这个值,就把这个N-1个的数据放入map的内存里,进行mapjoin。
再查询参数hive.auto.convert.join.noconditionaltask.size的值,结果为1431655765,是1.33G(不要问我为什么设置的这么大,我也不知道),这个参数的默认值是10M,这个参数怎么这么大? 那么意思就是:N-1个表的总和如果小于1.3G就启动mapjoin。到这里就知道了,s_test表的数据是200M,启动mapjoin就正常了。
修改hive.auto.convert.join.noconditionaltask.size参数的值,改小这个值,比如改为默认的10M,就不会启动MapJoin了。另外设置hive.auto.convert.join为false也可以停用mapjoin,但是这种方式不好,它会停用所有的mapjoin。
难道是这个hive sql 不会到yarn上去执行,而是用本地模式执行?那么查一下官网文档mapjoin执行的详细步骤:
(1) 再启动mapreduce之前,先启动一个local task,这个task是从HDFS上读取数据变为hashtable的形式到内存中。
(2) 将hashtable数据进行序列化存入硬盘,并且对数据进行压缩为一个tar文件。
(3) 然后将这个tar文件传递到分布式缓存Hadoop Distributed Cache中。
(4) 将这个tar文件分发到各个mapper中,存入硬盘并进行解压,读取到内存中。
(5) 各个map中读取这个hashtable进行join操作。
通过这个过程可以看到,出错的hive sql运行日志中显示的local task是mapjoin第一步中产生的,这个local task是用来读取文件并生成hashtable,读取200M的orc数据并展开读取本地内存中,会有非常大的数据量,导致内存溢出也就不足为奇了。
总而言之,这个问题的根本原因在于hive.auto.convert.join.noconditionaltask.size参数设置的过大,减小这个值就可以了。