HIVEMapJoin異常問題處理總結

計算愛好者發表於2016-11-16

問題描述

在跑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

搜尋結果建議的解決方案

    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() + "	Processing rows:	" + numRows + "	Hashtable size:	"
        + tableContainerSize + "	Memory usage:	" + usedMemory + "	percentage:	" + 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


相關文章