리눅스 OOM Killer(Out of Memory) 동작 원리

리눅스 OOM Killer(Out of Memory) 동작 원리와 메모리 누수 방지 튜닝 가이드

리눅스 서버를 운영하다 보면 분명 물리 메모리가 충분하다고 생각했는데 중요한 데이터베이스나 애플리케이션 프로세스가 갑자기 강제 종료(Killed)되는 당혹스러운 상황을 겪곤 합니다.

이러한 현상의 배후에는 리눅스 커널의 최후 안전장치인 OOM 킬러(Out of Memory Killer)가 동작하고 있습니다. 시스템 전체의 다운을 막기 위해 프로세스를 강제로 희생시키는 OOM Killer의 내부 동작 원리부터 vm.overcommit_memory, vm.swappiness 등 핵심 커널 파라미터 튜닝과 스왑(Swap) 최적화 방안까지 알기 쉽게 정리해 드립니다.

1. OOM Killer의 개념과 동작 원리

리눅스는 한정된 메모리 자원을 최대한 효율적으로 활용하기 위해 프로세스가 요청한 메모리를 즉시 물리 메모리에 할당하지 않고, 실제 데이터를 쓸 때 할당하는 오버커밋(Overcommit) 정책을 기본으로 채택하고 있습니다.

이 방식은 가용 메모리를 극대화할 수 있다는 장점이 있지만, 여러 프로세스가 동시에 할당받은 가상 메모리에 실제 데이터를 기록하기 시작하면 순간적으로 물리 메모리와 스왑 영역이 완전히 고갈되는 상황이 발생합니다.

시스템 메모리가 완전히 고갈되어 커널 자체조차 동작하기 어려운 상태에 도달하면, 리눅스 커널은 전체 시스템이 멈추는 커널 패닉(Kernel Panic)을 방지하기 위해 특정 프로세스를 강제로 강제 종료하여 메모리를 즉시 확보합니다. 이 메커니즘을 바로 OOM Killer라고 부릅니다.

OOM Killer는 무작위로 프로세스를 종료하는 것이 아니라, 각 프로세스의 OOM 점수(oom_score)를 계산하여 가장 점수가 높은 프로세스를 희생양으로 선택합니다.

  • oom_score 산정 기준: 프로세스가 점유하고 있는 실제 물리 메모리(RSS)의 크기가 클수록, 그리고 시스템에 머문 시간이 짧거나 불필요한 자원을 많이 소모할수록 높은 점수가 부여됩니다.

  • oom_score_adj를 통한 우선순위 제어: 시스템에서 절대 꺼지면 안 되는 핵심 프로세스(예: MySQL, Redis, Nginx 등)는 /proc/[PID]/oom_score_adj 값을 -1000으로 설정하여 OOM Killer의 종료 대상에서 완전히 제외하거나 점수를 낮출 수 있습니다.

2. 메모리 오버커밋 제어: vm.overcommit_memory 튜닝

메모리 고갈로 인한 급작스러운 프로세스 종료를 예방하기 위해 가장 먼저 검토해야 할 설정이 바로 커널의 오버커밋 정책입니다.

/etc/sysctl.conf 파일에서 vm.overcommit_memory 값을 조정하여 메모리 할당 방식을 환경에 맞게 제어할 수 있습니다.

  • vm.overcommit_memory = 0 (기본값, Heuristic Overcommit): 커널이 시스템의 여유 메모리와 휴리스틱 알고리즘을 바탕으로 적절하다고 판단되는 수준까지만 오버커밋을 허용합니다. 대부분의 범용 서버 환경에 기본 적용됩니다.

  • vm.overcommit_memory = 1 (Always Overcommit): 프로세스가 요청하는 모든 가상 메모리 할당 요청을 제한 없이 승인합니다. 메모리를 대량으로 미리 할당해 두고 사용하는 Redis나 특정 인메모리 데이터베이스 환경에서 필수적으로 권장되는 설정입니다.

  • vm.overcommit_memory = 2 (Strict / No Overcommit): 물리 메모리와 스왑 영역을 초과하는 메모리 할당을 엄격히 차단합니다. 메모리 초과 시 신규 프로세스 할당 요청에 대해 ENOMEM(Out of Memory) 에러를 반환하므로 OOM Killer에 의해 기존 프로세스가 강제 종료되는 사태를 원천 차단할 수 있습니다.

엄격 모드(mode 2)를 사용할 때는 vm.overcommit_ratio 파라미터도 함께 고려해야 합니다. 예를 들어 vm.overcommit_ratio = 50으로 설정하면 최대 할당 가능 메모리는 (스왑 메모리) + (물리 메모리 × 50%)로 제한됩니다.

3. 스왑(Swap) 영역 최적화 및 vm.swappiness 설정

스왑(Swap)은 물리 메모리가 부족할 때 디스크의 일정 공간을 메모리처럼 활용하는 완충 지대 역할을 합니다. 디스크 I/O 속도가 물리 RAM에 비해 현저히 느리기 때문에 과도한 스왑 사용은 서버 응답 지연(I/O Bottleneck)을 유발하지만, 적절한 스왑 공간 확보는 OOM Killer의 급작스러운 트리거를 완충해 주는 안전판이 됩니다.

커널이 얼마나 적극적으로 물리 메모리의 데이터를 스왑 영역으로 이동시킬지를 결정하는 파라미터가 vm.swappiness입니다. (값 범위: 0 ~ 100)

  • vm.swappiness = 60 (기본값): 일반적인 데스크톱 및 범용 리눅스 환경에 맞춰진 기본값으로, 페이지 캐시와 익명 메모리를 균형 있게 스왑합니다.

  • vm.swappiness = 10 또는 1 (데이터베이스 및 고성능 서버 권장): 디스크 스왑 발생을 최대한 억제하고 물리 메모리를 끝까지 활용하도록 유도합니다. 메모리 응답 속도가 중요한 DB 서버(MySQL, PostgreSQL 등)나 실시간 트래픽 처리 서버에서 필수적으로 적용하는 설정입니다.

  • vm.swappiness = 0: 물리 메모리와 파일 캐시 영역이 완전히 고갈되기 전까지는 스왑을 거의 사용하지 않습니다. (단, 메모리가 완전히 바닥나면 OOM 방지를 위해 여전히 스왑이 발생할 수 있습니다.)

4. 메모리 누수(Memory Leak) 방지 및 실무 점검 가이드

서버 운영 중 OOM Killer가 호출되었다면 단순한 설정 변경에 그치지 않고 실제 애플리케이션의 메모리 누수 여부를 면밀히 추적해야 합니다.

  • 시스템 로그 분석: OOM 발생 시 커널은 /var/log/messages 또는 dmesgOut of memory: Kill process [PID] 형태의 상세 로그를 남깁니다. 당시 종료된 프로세스와 메모리 사용 현황을 확인하여 원인 프로세스를 특정합니다.

  • 메모리 메트릭 모니터링: free -m, vmstat 1, top 명령어를 통해 버퍼/캐시 영역(Buff/Cache)과 실제 프로세스가 점유 중인 메모리(RSS)의 증가 추이를 실시간으로 관찰합니다. 시간이 지남에 따라 메모리 반환 없이 지속적으로 점유량이 상승한다면 코드 레벨의 메모리 누수를 의심해야 합니다.

  • cgroup을 통한 컨테이너 메모리 제한: 도커(Docker)나 쿠버네티스(K8s) 환경에서는 단일 파드(Pod)나 컨테이너의 메모리 폭주가 호스트 노드 전체의 OOM으로 번지지 않도록 리소스 limitrequest를 명확히 정의하여 컨테이너 단위 격리를 구축해야 합니다.

Leave a Comment