HDFS 에서 말하는 GC 튜닝은 NameNode, DataNode, JournalNode 의 JVM 가비지 컬렉션을 뜻한다. NameNode 는 네임스페이스 전체를 힙에 올리므로 힙이 크고, Full GC 가 길어지면 ZKFC 가 응답 없음으로 판단해 페일오버를 유발한다. RegionServer 나 NodeManager 가 heartbeat 를 놓치는 연쇄 장애의 출발점이 되기도 한다.
큰 힙에서는 G1GC 를 쓴다. Java 8 이후 사실상 표준이다. 설정은 hadoop-env.sh 에서 데몬별 옵션 변수에 넣는다.
export HDFS_NAMENODE_OPTS="-XX:+UseG1GC -Xms16g -Xmx16g \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=45 \
-XX:G1ReservePercent=10 \
$HDFS_NAMENODE_OPTS"
export HDFS_DATANODE_OPTS="-XX:+UseG1GC -Xms4g -Xmx4g $HDFS_DATANODE_OPTS"
export HDFS_JOURNALNODE_OPTS="-XX:+UseG1GC -Xms2g -Xmx2g $HDFS_JOURNALNODE_OPTS"
Hadoop 3.x 는 HDFS_NAMENODE_OPTS 형태를 쓴다. 2.x 에서 쓰던 HADOOP_NAMENODE_OPTS 는 3.x 에서 더 이상 표준이 아니다. Cloudera Manager 나 Ambari 로 관리하는 클러스터라면 hadoop-env.sh 를 직접 고치지 말고 관리 도구의 Java Heap Size 와 Java Configuration Options 항목에 넣는다. 배포 때 파일이 덮어써진다.
-Xms 와 -Xmx 를 같게 두는 이유는 힙 확장 과정에서 발생하는 지연을 없애기 위해서다.
NameNode 힙은 네임스페이스 크기에 비례한다. 파일과 블록 수가 늘수록 커져야 하며, 실측 없이 늘리기보다 현재 사용량을 보고 정한다.
jstat -gcutil <NN_PID> 5s 5
jmap -heap <NN_PID> | head -30
hdfs dfsadmin -report | head -20
jstat 의 O(Old 영역 사용률)가 Full GC 이후에도 높은 수준에서 내려오지 않으면 힙이 부족한 것이다. DataNode 는 메타데이터를 많이 들지 않으므로 대체로 4~8GB 면 충분하다.
원인 분석을 하려면 로그가 남아 있어야 한다. Java 9 이상은 통합 로깅 옵션을 쓴다.
export HDFS_NAMENODE_OPTS="$HDFS_NAMENODE_OPTS \
-Xlog:gc*,safepoint:file=/var/log/hadoop-hdfs/gc-namenode.log:time,uptime:filecount=10,filesize=64M"
Java 8 이하에서만 아래 형태를 쓴다. Java 9 이상에서는 제거된 옵션이라 프로세스가 뜨지 않는다.
export HDFS_NAMENODE_OPTS="$HDFS_NAMENODE_OPTS \
-XX:+PrintGCDetails -XX:+PrintGCDateStamps \
-Xloggc:/var/log/hadoop-hdfs/gc-namenode.log"
-Xlog 의 filecount/filesize 로 회전을 맡기면 별도 logrotate 가 필요 없다. Java 8 형태를 쓴다면 copytruncate 방식의 logrotate 를 둔다.
GC 옵션을 바꾸면 데몬 재시작이 필요하다. HA 구성에서는 Standby 부터 재시작하고 페일오버 후 나머지를 처리한다. 옵션을 한 번에 여러 개 바꾸면 어떤 변경이 효과를 냈는지 판단할 수 없으므로, 힙 크기와 컬렉터를 먼저 정리한 뒤 세부 튜닝으로 넘어간다.