HIVE MapJoin异常问题处理总结

问题描述

在跑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 等等这样的日志。在网上搜索也可以看到一些问题的解释,例如 *上就有一个 http://*.com/questions/22977790/hive-query-execution-error-return-code-3-from-mapredlocaltask

搜索结果建议的解决方案

    1. set hive.auto.convert.join = false; 关闭mapjion
    1. 调小hive.smalltable.filesize,默认是25000000(在2.0.0版本中)
    1. hive.mapjoin.localtask.max.memory.usage 调大到0.999
    1. 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

上一篇:城市大脑 | 智慧监管解决方案


下一篇:E-MapReduce集群启停HDFS/YARN服务