스터디
5
2024년 1월 15일

Apache Ambari Hadoop서비스에서 겪었던 문제 & 해결 review - Heap 메모리 부족

#Hadoop#Spark#Hive

지난 인프라 구조는 다음과 같았습니다.

  • Bastion Host 구조
  • Namenode 1대
  • 12대의 datanode

해당 인프라 구조에서 주로 2개의 게임 서비스(Elyon, TERA)를 운영하였고 평균 쿼리 사이즈가 1GB를 상회했었습니다.

Hadoop Ecosystem Architecture

( 그림1. Bastion Host - Apache Ambari Hadoop Ecosystem Architecture )

그러나 해당 인프라를 운영하면서 유독 Heap 메모리 부족으로 많은 인프라 장애를 앓고 있었습니다. 관련 유력 원인은 다음과 같습니다.

  1. YARN NodeManager/ResourceManager 메모리 부족
  • 컨테이너 할당이 물리 메모리를 초과할 때
  • yarn.nodemanager.resource.memory-mb 설정이 실제 가용 메모리보다 클 때
  • Java heap이 작은데 많은 애플리케이션이 동시 실행될 때
  1. MapReduce Job의 메모리 설정 문제
  • Map/Reduce task의 heap size가 너무 작게 설정 (mapreduce.map.java.opts, mapreduce.reduce.java.opts)
  • 대용량 데이터 처리 시 메모리 버퍼 부족
  • Shuffle 단계에서 메모리 초과
  1. HBase RegionServer OOM
  • MemStore 크기 설정이 부적절할 때
  • BlockCache와 MemStore 합이 heap의 80%를 초과할 때
  • 대량의 동시 요청 처리 시
  1. Hive/Tez 실행 엔진
  • Tez AM (Application Master) heap 부족
  • 복잡한 쿼리의 실행 계획 생성 시
  • 조인이나 집계 연산의 중간 결과가 메모리에 쌓일 때

그러나 당시 상황 로그 분석 및 자료들 분석 결과, 1GB 정도 쿼리를 돌리는 환경에서 YARN 메모리 부족이 자주 발생했다면, 전형적인 메모리 오버커밋(over-commit) 문제였을 가능성이 높습니다.

당시 상황 분석

xlarge 인스턴스 (보통 4 vCPU, 16GB RAM 기준)

  • 실제 가용 메모리: ~14GB (OS, 데몬 제외)
  • DataNode가 사용: ~2GB
  • NodeManager가 컨테이너에 할당 가능: 이론상 ~12GB

문제 발생 시나리오:

yarn.nodemanager.resource.memory-mb = 12288 (12GB)
mapreduce.map.memory.mb = 2048 (2GB)
mapreduce.reduce.memory.mb = 4096 (4GB)

→ Map task 6개 or Reduce task 3개까지만 가능
→ 하지만 Java heap + off-heap 메모리가 실제로는 더 사용됨

1GB 쿼리에서 왜 문제가?

Multiple Map tasks 동시 실행

1GB 데이터 → HDFS block 단위(128MB)로 나뉨 → 약 8개 Map task 각 Map task가 2GB 메모리 요청 노드당 6개만 가능한데 8개가 스케줄링되면 대기/재시도

Reduce 단계의 메모리 압박

Shuffle/Sort 단계에서 메모리 버퍼 사용 mapreduce.reduce.shuffle.memory.limit.percent (기본 0.25) 4GB reduce task의 경우 실제 heap 3GB + shuffle buffer 1GB = 실제 4GB+ 사용

Container 메모리 vs Java Heap 불일치

mapreduce.map.memory.mb = 2048
mapreduce.map.java.opts = -Xmx1638m  (80% 권장)

→ 나머지 400MB는 off-heap, overhead
→ 실제로는 2GB 이상 사용하는 경우 발생

전형적인 에러 로그

Container killed by YARN for exceeding memory limits. 
2.1 GB of 2 GB physical memory used.

당시 해결 방법들

즉시 조치:

  • yarn.nodemanager.resource.memory-mb를 10GB로 축소
  • yarn.nodemanager.vmem-check-enabled=false (임시 우회)

근본적 해결:

  • Map/Reduce task 메모리를 더 여유있게 (2GB → 3GB)
  • 동시 실행 task 수 제한
  • DataNode heap 메모리 최적화 (불필요하게 크면 줄임)

위의 근본적 해결방법 중중 Map/Reduce task 메모리를 더 여유있게 (2GB → 3GB) 해당 내용에 대한 조치를 순서대로 알아보겠습니다.

Ambari UI에서 변경 (가장 일반적)

  1. MapReduce2 설정

Ambari Web UI → Services → MapReduce2 → Configs → Advanced

[Memory 관련 설정]

mapreduce.map.memory.mb = 3072 (2048 → 3072) mapreduce.reduce.memory.mb = 6144 (4096 → 6144)

[Java Heap 설정 - 80% 규칙]

mapreduce.map.java.opts = -Xmx2457m (80% of 3072) mapreduce.reduce.java.opts = -Xmx4915m (80% of 6144)

  1. YARN 설정도 함께 확인

Services → YARN → Configs

yarn.scheduler.minimum-allocation-mb = 1024 yarn.scheduler.maximum-allocation-mb = 8192 (task가 요청 가능한 최대치) yarn.nodemanager.resource.memory-mb = 10240 (노드당 할당 가능 총량)

설정 파일로 직접 변경 (모든 노드)

mapred-site.xml (보통 /etc/hadoop/conf/)

<property>
  <name>mapreduce.map.memory.mb</name>
  <value>3072</value>
</property>

<property>
  <name>mapreduce.reduce.memory.mb</name>
  <value>6144</value>
</property>

<property>
  <name>mapreduce.map.java.opts</name>
  <value>-Xmx2457m</value>
</property>

<property>
  <name>mapreduce.reduce.java.opts</name>
  <value>-Xmx4915m</value>
</property>

Job 실행 시 동적으로 변경 (임시 테스트용)

Hive 쿼리 실행 전:

SET mapreduce.map.memory.mb=3072;
SET mapreduce.reduce.memory.mb=6144;
SET mapreduce.map.java.opts=-Xmx2457m;
SET mapreduce.reduce.java.opts=-Xmx4915m;

SELECT ... -- 실제 쿼리

Spark submit 시:

spark-submit \
  --conf spark.executor.memory=3g \
  --conf spark.driver.memory=2g \
  your_job.py

변경 후 필수 작업

# Ambari에서 변경 시:
1. "Save" 클릭
2. "Restart All Required Services" (NodeManager 재시작 필요)

# 직접 변경 시:
ambari-server restart
# 또는
sudo systemctl restart hadoop-yarn-nodemanager

실전 팁

메모리 계산 공식:

Java Heap = Container Memory × 0.8

예시:
Container 3GB (3072MB) → Heap 2457MB (-Xmx2457m)
Container 6GB (6144MB) → Heap 4915MB (-Xmx4915m)

왜 80%인가?

  • 나머지 20%는 JVM overhead, off-heap, native memory 사용
  • 100%로 설정하면 Container killed 발생