← 목록

ECS Fargate OOM 방지: 메모리 설정 최적화

ECS Fargate 태스크가 OOM(Out of Memory)으로 반복 종료되는 문제를 분석하고, 메모리 설정 최적화로 해결한 과정을 정리한다.

2025-01-07AWSECSTroubleshootingDevOps

증상

ECS Fargate에서 운영 중인 서비스의 태스크가 간헐적으로 종료되는 현상이 발생했다.


원인 분석

1. Task Definition 메모리 설정 확인

{
  "memory": "512",
  "containerDefinitions": [
    {
      "memory": 512,
      "memoryReservation": 256
    }
  ]
}

Task 레벨 메모리와 컨테이너 메모리가 동일하게 512MB로 설정되어 있어, 컨테이너가 사용할 수 있는 여유 메모리가 전혀 없는 상태였다.

2. JVM 힙 메모리 미설정

Java 기반 애플리케이션이었으나 JVM 힙 메모리(-Xmx, -Xms)가 명시적으로 설정되지 않아, JVM이 컨테이너 메모리 전체를 힙으로 사용하려 시도했다.

3. CloudWatch 메트릭 확인

CloudWatch의 MemoryUtilization 메트릭을 확인한 결과, 평상시 70~80%를 유지하다가 피크 시 95% 이상으로 치솟는 패턴이 확인되었다.


해결 방법

1. Task Definition 메모리 상향

{
  "memory": "1024",
  "containerDefinitions": [
    {
      "memory": 1024,
      "memoryReservation": 512
    }
  ]
}

2. JVM 힙 메모리 명시 설정

Dockerfile 또는 Task Definition의 환경 변수에 JVM 옵션을 추가했다.

JAVA_OPTS="-Xms256m -Xmx768m -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

Heap 제한의 핵심: "죽는 서비스"를 "느려지기만 하는 서비스"로

JVM Heap을 컨테이너 메모리의 60~70%로 제한하는 이유는 단순히 메모리 절약이 아니다. OOM으로 프로세스가 죽는 것을 GC로 느려지기만 하는 것으로 바꾸는 것이 핵심이다.

설정 안 했을 때

JVM Heap이 컨테이너 메모리 끝까지 사용
          ↓
Native / Stack / Metaspace 공간 부족
          ↓
Linux OOM Killer 작동
          ↓
컨테이너 즉시 종료 (Exit 137)
          ↓
ECS가 태스크 교체 → 순간 서비스 영향

설정했을 때 (60~70%)

Heap이 미리 제한됨
          ↓
메모리 압박 시 GC 먼저 발생
          ↓
응답 느려질 수는 있지만 프로세스는 살아 있음
          ↓
헬스체크 실패 확률 급감

📌 운영 관점에서 "잠깐 느려짐"은 감당 가능하지만, "프로세스 종료"는 장애이다.


3. CloudWatch 알람 설정

OOM 재발 방지를 위해 메모리 사용률 기반 알람을 추가했다.

MemoryUtilization >= 85% → Warning (Slack 알림)
MemoryUtilization >= 95% → Critical (전화 알림)

결과


교훈


배운 점

컨테이너에 메모리를 512MB 줬다고 애플리케이션이 512MB를 쓰는 게 아니다. JVM은 힙 말고도 Metaspace, 스레드 스택, 네이티브 버퍼를 따로 쓰기 때문에, 힙 상한을 컨테이너 한계에 맞춰두면 남는 공간이 없어진다. OOM은 대개 계산을 잘못한 결과지 부하가 커서 생기는 일이 아니었다.

Task 메모리와 컨테이너 메모리를 같은 값으로 둔 설정도 이때 고쳤다. 여유분 없이 꽉 채워두면 조금만 튀어도 컨테이너가 통째로 죽는다.

숫자를 정하고 나서 필요한 건 그 숫자가 맞았는지 확인할 눈이었다. 그래서 ECS Fargate 환경 JVM 모니터링 고도화로 이어졌고, 나중에 ECS 강제 재배포 후 메모리 사용률 급증에서 같은 개념이 다시 등장한다.


이어 읽기