Hadoop Job Counter 개수 제한 초과 오류
www.recopick.com

안녕하세요. RecoPick 팀의 김성민입니다. 

이번에는 Hadoop map-reduce 작업 중 발생한 특이한(?) 에러에 대해서 공유 드리고자 합니다. 

팀 업무 특성상 로그 기록 분석을 위한 통계 작업을 많이 하는데, 아주 단순한 통계 작업을 하다가 아래와 같은 에러가 발생했습니다.


현상


HTTP ERROR 500

Problem accessing /jobdetailshistory.jsp. Reason:
    Too many counters: 121 max=120


Caused by:
org.apache.hadoop.mapreduce.counters.LimitExceededException: Too many counters: 121 max=120
        at org.apache.hadoop.mapreduce.counters.Limits.checkCounters(Limits.java:61)
        at org.apache.hadoop.mapreduce.counters.Limits.incrCounters(Limits.java:68)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.addCounter(AbstractCounterGroup.java:77)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.addCounterImpl(AbstractCounterGroup.java:94)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounterImpl(AbstractCounterGroup.java:122)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounter(AbstractCounterGroup.java:112)
        at org.apache.hadoop.mapreduce.counters.AbstractCounterGroup.findCounter(AbstractCounterGroup.java:129)
        at org.apache.hadoop.mapred.Counters$Group.findCounter(Counters.java:323)
        at org.apache.hadoop.mapred.Counters$Group.findCounter(Counters.java:217)
        at org.apache.hadoop.mapreduce.util.CountersStrings.parseEscapedCompactString(CountersStrings.java:272)
        at org.apache.hadoop.mapred.Counters.fromEscapedCompactString(Counters.java:595)
        at org.apache.hadoop.mapred.jobdetailshistory_jsp._jspService(jobdetailshistory_jsp.java:339)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:98)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
        at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1221)
        at org.apache.hadoop.http.lib.StaticUserWebFilter$StaticUserFilter.doFilter(StaticUserWebFilter.java:109)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.HttpServer$QuotingInputFilter.doFilter(HttpServer.java:1068)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.NoCacheFilter.doFilter(NoCacheFilter.java:45)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.apache.hadoop.http.NoCacheFilter.doFilter(NoCacheFilter.java:45)
        at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1212)
        at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:399)
        at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
        at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
        at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:766)
        at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:450)
        at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
        at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
        at org.mortbay.jetty.Server.handle(Server.java:326)
        at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
        at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:928)
        at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:549)
        at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212)
        at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
        at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)  
        at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)


원인

에러 발생 원인은 "Too many counters: 121 max=120" 에러 로그 메시지처럼, Job Tracker에서 사용하는 Counter 최대 개수를 초과해서, Job Tracker 웹 UI에서 작업 history를 볼 수 없었습니다. 

실제로 Exception이 발생하는 부분의 코드를 추적해 보면 알 수 있듯이 map reduce·job.counters.limit 기본 설정 값이 120입니다.


[MRJobConfig.java 소스 코드]

package org.apache.hadoop.mapreduce;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;

@InterfaceAudience.Private
@InterfaceStability.Evolving
public interface MRJobConfig {

  public static final String COUNTERS_MAX_KEY = "mapreduce.job.counters.max";
  public static final int COUNTERS_MAX_DEFAULT = 120;

  public static final String COUNTER_GROUP_NAME_MAX_KEY = "mapreduce.job.counters.group.name.max";
  public static final int COUNTER_GROUP_NAME_MAX_DEFAULT = 128;

  public static final String COUNTER_NAME_MAX_KEY = "mapreduce.job.counters.counter.name.max";
  public static final int COUNTER_NAME_MAX_DEFAULT = 64;

  public static final String COUNTER_GROUPS_MAX_KEY = "mapreduce.job.counters.groups.max";
  public static final int COUNTER_GROUPS_MAX_DEFAULT = 50;


[Limits.java 소스 코드]

package org.apache.hadoop.mapreduce.counters;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import static org.apache.hadoop.mapreduce.MRJobConfig.*;

@InterfaceAudience.Private
public class Limits {

  static final Configuration conf = new Configuration();
  public static final int GROUP_NAME_MAX =
      conf.getInt(COUNTER_GROUP_NAME_MAX_KEY, COUNTER_GROUP_NAME_MAX_DEFAULT);
  public static final int COUNTER_NAME_MAX =
      conf.getInt(COUNTER_NAME_MAX_KEY, COUNTER_NAME_MAX_DEFAULT);
  public static final int GROUPS_MAX =
      conf.getInt(COUNTER_GROUPS_MAX_KEY, COUNTER_GROUPS_MAX_DEFAULT);
  // mapreduce.job.counters.limit is deprecated in favor of
  // mapreduce.job.counters.max in Hadoop 2 so we support both here
  public static final int COUNTERS_MAX =
      conf.getInt(COUNTERS_MAX_KEY,
        conf.getInt("mapreduce.job.counters.limit", COUNTERS_MAX_DEFAULT));

  private int totalCounters;
  private LimitExceededException firstViolation;

  // ...중략...

  public synchronized void checkCounters(int size) {
    if (firstViolation != null) {
      throw new LimitExceededException(firstViolation);
    }
    if (size > COUNTERS_MAX) {
      firstViolation = new LimitExceededException("Too many counters: "+ size +
                                                  " max="+ COUNTERS_MAX);
      throw firstViolation;
    }
  }

Hadoop map-reduce 작업 시에는 Job Tracker와 Task Tracker가 관리하는 기본 counter 외에 사용자가 필요에 따라서 counter를 추가할 수 있습니다.


예를 들어 서비스별 PV/UV와 같은 단순한 통계 계산을 할 때, 결과를 HDFS에 남기는 것 보다, map-reduce의 counter로 남기면, HDFS에 결과를 쓸 필요 없이 바로 계산 결과를 가져올 수 있습니다. 그런데 서비스 개수가 120개 이하일 때는 문제가 발생하지 않지만, 서비스 개수가 120개를 초과할 경우, job tracker의 counter 제한에 걸려서 위와 같은 에러가 발생하게 됩니다.


해결 방법


사용자 counter 개수가 120개가 초과할 것으로 예상한다면, 결과를 HDFS에 남기는 것이 좋습니다. 

혹은, job tracker를 실행시킬 때, job counter 최대 개수를 충분히 크게 하면 이런 문제는 회피할 수 있을 것입니다. 하지만 job counter가 job tracker의 memory에 보관되기 때문에, job counter 최대 개수를 늘릴수록 job tracker 실행하기 위해 더 많은 메모리가 필요하다는 점을 유의하셔야 합니다. 


또한, Hadoop 버전 별로 기본 설정 값이 다르므로, 사용하는 Hadoop 버전의 설정을 미리 확인하시는 편이 좋습니다. 


다음에도 Hadoop 사용할 때, 경험하기 힘든(?) 에러를 볼 경우, 블로그를 통해서 공유 드리도록 하겠습니다.



[참고 자료]

Posted by recopick

댓글을 달아 주세요