问题描述
在跑hive作业的时候,偶尔会遇到下面的异常 FAILED: Execution Error, return code 3 from org.apache.hadoop.hive.ql.exec.mr.MapredLocalTask 。通过查看日志,你可以看到这是map join的问题,会看到Starting to launch local task to process map join; maximum memory = xxx,Execution failed with exit status: 3 等等这样的日志。在网上搜索也可以看到一些问题的解释,例如 stackoverflow上就有一个 http://stackoverflow.com/questions/22977790/hive-query-execution-error-return-code-3-from-mapredlocaltask
搜索结果建议的解决方案
-
- set hive.auto.convert.join = false; 关闭mapjion
-
- 调小hive.smalltable.filesize,默认是25000000(在2.0.0版本中)
-
- hive.mapjoin.localtask.max.memory.usage 调大到0.999
-
- set hive.ignore.mapjoin.hint=false; 关闭忽略mapjoin的hints
原理及问题分析
MapJoin原理可以参见这里,讲的比较清楚。出现问题的地方就是MapredLocalTask这里,在客户端本地启动一个Driver进程,扫描小表的数据,将其转换成一个HashTable的数据结构,这个过程中在做内存检查,即checkMemoryStatus的时候,抛出了异常。我们看一下这里的检查点
double percentage = (double) usedMemory / (double) maxHeapSize;
String msg = Utilities.now() + "\tProcessing rows:\t" + numRows + "\tHashtable size:\t"
+ tableContainerSize + "\tMemory usage:\t" + usedMemory + "\tpercentage:\t" + percentageNumberFormat.format(percentage);
console.printInfo(msg);
if(percentage > maxMemoryUsage) {
throw new MapJoinMemoryExhaustionException(msg);
}
跟当前进程的MaxHeap有关,跟当前进程的UsedMemory有关,跟参数maxMemoryUsage有关(hive.mapjoin.localtask.max.memory.usage),通过分析比较我们可以发现,上述的方案1和4,直接关闭mapjion,避免启动MapredLocalTask,就不会出现这样的check,进而不会出现问题;上述的方案2,减小join表的大小,进而减小UsedMemory,也可以解决这个问题;上面的方案3, 调大maxMemoryUsage,使内存充分利用,也可以解决这个问题。我们注意到maxHeapSize 这个参数,没有针对性的解决方案
增加的一种解决方案,调大MapredLocalTask JVM启动参数
解决方案还是需要考虑不影响性能。
调大MapredLocalTask 的JVM启动参数,进而可以增加maxHeapSize,同样也可以解决这个问题。如何去调大这个参数呢?通过查看MapredLocalTask代码我们可以看到
jarCmd = hiveJar + " " + ExecDriver.class.getName();
String hiveConfArgs = ExecDriver.generateCmdLine(conf, ctx);
String cmdLine = hadoopExec + " jar " + jarCmd + " -localtask -plan " + planPath.toString()
+ " " + isSilent + " " + hiveConfArgs;
...
Map<String, String> variables = new HashMap<String, String>(System.getenv());
...
// Run ExecDriver in another JVM
executor = Runtime.getRuntime().exec(cmdLine, env, new File(workDir));
启动新的ExecDriver,使用的是hadoop jar,系统环境参数继承了父进程的系统环境变量(里面逻辑有一些参数会覆盖)。而hadoop jar 启动java进程,内存参数会受哪些地方影响呢?如果没有设置,受hadoop自身一些脚本配置的影响;HADOOP_HEAPSIZE,如果设置了该变量,JVM参数就是-Xmx${HADOOP_HEAPSIZE}m ;如果不设置 ,就会受/usr/lib/hadoop-current/libexec/hadoop-config.sh里面配置的JAVA_HEAP_MAX=-Xmx1000m 。有没有印象?你使用hadoop jar启动的一些进程参数都是-Xmx1000m, 如果注意观察,ExecDriver这个进程也是这个参数。知道这个参数之后,可以在/usr/lib/hadoop-current/libexec/hadoop-config.sh 这里将参数调大,例如设置JAVA_HEAP_MAX=-Xmx1408m 可以解决问题。
研究与思考
通过查看checkMemoryStatus 的代码,我们可以看到,这个比较的逻辑不太合适,当前内存使用达到了一定阈值,并不代表内存不够用,因为还有gc存在啊,如果gc之后还是超过了这个阈值,确实需要抛出异常。基于这样的分析,在HIVE JIRA上提了一个issue 并有相应的一些想法和patch。如果感兴趣,欢迎讨论交流,请戳HIVE-15221