지난 2월 9일 오후 1시 41분. 호스트 시스템에서 Python 워커 프로세스 하나가 갑자기 죽었다. 원인은 OOM killer — 메모리 부족으로 시스템이 스스로 프로세스를 강제 종료한 것이다.

문제의 신호들

커널 로그를 펼쳐보니 상황은 심각했다.

메모리 상태:

  • 활성 익명 메모리: 6.7GB 점유
  • Swap 메모리: 완전 소진 (0kB)
  • 시스템 여유: 약 31MB (극도로 위험)

Python 워커들의 점유:

단일 프로세스가 1.6GB씩 점유하는 상황이었다. 그리고 더 충격적인 건, 이건 새로운 코드 배포 이후부터 시작된 문제라는 것.

근본 원인: 코드 메모리 누수

개발자가 새로운 소스 코드를 적용한 지 얼마 지나지 않아 발생한 현상이다. Python 워커 프로세스들이 지속적으로 메모리를 할당하지만 해제하지 않는 전형적인 메모리 누수 패턴.

이미지 처리나 데이터 변환 같은 무거운 작업을 반복할 때 자주 발생하는 문제다.

응급 대응: 재부팅

이 시점에서 선택지는 두 가지였다.

  1. 빠른 복구: 시스템 재부팅 → 즉시 정상화
  2. 근본 분석: 메모리 덤프 수집 후 분석 → 시간 소요, 서비스 중단

인프라 엔지니어의 원칙은 명확하다. 빠른 복구가 최우선. 원인 분석은 나중이다. 그래서 재부팅했다.

교훈: 3가지 수준의 방어선

이 상황에서 배운 점들:

1. 메모리 모니터링의 중요성

초기 징후를 놓쳤다면 OOM killer까지 가지 않았을 것이다. 필수 조치:

  • 크론잡 기반 정기 모니터링 (5분마다 체크)
  • 임계값 도달 시 자동 알림 설정
  • 메모리 사용 추세 추적

2. 프로세스 제한 설정

메모리 제한이 있었다면, 단일 프로세스가 1.6GB를 점유하지 못했을 것이다. 여러 방법이 있다.

방법 1: systemd service 파일 (권장)

1
2
3
4
[Service]
MemoryLimit=2G          # 소프트 제한
MemoryMax=2.5G          # 하드 제한 (초과 시 OOM killer)
TasksMax=100            # 프로세스 개수 제한

방법 2: /etc/security/limits.conf

1
2
3
4
5
# username을 app_user로 변경
app_user soft memlock unlimited
app_user hard memlock unlimited
app_user soft as 2097152  # 2GB (KB 단위)
app_user hard as 2097152

방법 3: cgroup 직접 설정

1
2
3
4
5
6
7
8
# cgroup 생성
cgcreate -g memory:/app_name

# 메모리 제한 (2GB = 2147483648 bytes)
echo 2147483648 > /sys/fs/cgroup/memory/app_name/memory.limit_in_bytes

# 프로세스 실행
cgexec -g memory:/app_name python app.py

방법 4: Docker 컨테이너라면

1
2
3
4
5
services:
  app:
    image: python:3.9
    mem_limit: 2g
    memswap_limit: 2.5g

오라클 DB에서 많이 본 건 아마 **방법 2 (limits.conf)**일 것이다. 가장 범용적이고 오래된 방식이기 때문이다.

3. 개발자와의 협력

  • 새 코드 배포 후 모니터링 기간 필수
  • 성능 테스트 환경에서 장시간 부하 테스트 필요
  • 메모리 프로파일링 도구 활용 (memory_profiler, tracemalloc 등)

현재 상태

  • 시스템: 재부팅으로 정상화됨
  • 개발자: 메모리 누수 원인 분석 중
  • 인프라: 메모리 모니터링 크론잡 추가 예정

결론

OOM killer를 만났다는 건 시스템이 마지막 자기방어 메커니즘을 발동했다는 뜻이다. 그 전에 감지하고 멈추는 게 진정한 모니터링이다.

다음부터는 메모리 한계 전에 손을 쓰자. 🛡️