Sandcorp샌드코프 · 웹·AI 제품 개발

새 프로젝트 받는 중 · 갱신 2026.11sandcorp.co.krGitHub

← 팁

메모리배포·인프라

earlyoom과 oom_score_adj로 서버가 멈추기 전에 「죽어도 되는 것」부터 끝낸다

커널 OOM 킬러는 서버가 이미 무응답이 된 뒤에야 움직인다. earlyoom으로 일찍 끝내고, 빌드·도구 컨테이너에 oom_score_adj=1000을 주어 먼저 고르게 하고, 메모리 상한을 건다.

문제

한 컨테이너가 몇 분 만에 메모리를 다 먹자, 커널 OOM 킬러가 움직이기도 전에 서버 전체가 응답하지 않게 됐습니다. ssh도 붙지 않아 클라우드 콘솔에서 강제 재부팅하는 수밖에 없었습니다.

원인

커널 OOM 킬러는 할당이 정말로 실패할 때에야 움직입니다. 그 직전에 커널은 페이지 캐시와 실행 파일 페이지까지 버리고 다시 읽기를 되풀이합니다. 이 구간에서 서버는 살아 있지만 아무 일도 못 합니다. 게다가 누구를 죽일지는 메모리를 많이 쓰는 순서라, DB처럼 원래 크게 쓰는 운영 프로세스가 먼저 골라질 수도 있습니다.

해법

  • earlyoom: 남은 메모리가 정한 비율 아래로 내려가면 사용자 공간에서 먼저 SIGTERM을 보냅니다. DB·sshd·dockerd처럼 죽으면 더 큰일인 프로세스는 --avoid로 뺍니다.
  • oom_score_adj=1000: 빌드 에이전트·헤드리스 브라우저처럼 비운영 컨테이너에 주면 earlyoom과 커널 모두 그쪽을 1순위로 고릅니다. compose 서비스라면 oom_score_adj로 줍니다. compose 밖에서 뜨는 컨테이너는 타이머로 주기적으로 다시 매깁니다(새로 뜬 프로세스도 잡히게).
  • 메모리 상한: 같은 서버에 빌드 도구를 둔다면 상한부터 겁니다. 상한이 있으면 폭주가 그 컨테이너 안에서 끝납니다.
# /etc/default/earlyoom — 남은 메모리 6%에서 SIGTERM, 핵심 프로세스는 피한다
EARLYOOM_ARGS="-m 6 --avoid '^(postgres|mysqld|redis-server|sshd|dockerd|containerd)$'"

# compose 밖 컨테이너: 타이머(예: 5분마다)로 다시 매기기
for c in build-agent headless-browser; do
  for pid in $(docker top "$c" -o pid | tail -n +2); do
    echo 1000 > "/proc/$pid/oom_score_adj"
  done
done
services:
  build-agent:
    mem_limit: 4g
    oom_score_adj: 1000

적용한 뒤에는 cat /proc/<pid>/oom_score로 비운영 프로세스의 점수가 가장 높은지 확인합니다.

문의 →