heap cap 합이 RAM 을 넘었다 — e2-medium 4GB 에서 PM2 메모리 재산정
PM2 cluster 5개 프로세스의 --max-old-space-size 합이 서버 RAM 을 초과하고 있었다. 평소엔 안 터지지만 스파이크 시 OOM. e2-medium 4GB 안에서 Node / 비-Node 예산을 나눠 재산정한 기록.
발견
GCE e2-medium (2 vCPU / 4GB RAM) 한 대에서 Next.js 3개 앱을 PM2 cluster 로 돌린다. 운영 모니터를 만들다 메모리 설정을 점검했더니:
# ecosystem.config.js (수정 전)
riel_agent: instances 2, --max-old-space-size=896 → 1792MB
riel_chat: instances 2, --max-old-space-size=896 → 1792MB
riel_secret: instances 1, --max-old-space-size=512 → 512MB
─────────────────────────────────────────────────
heap cap 합계 4096MB
서버 RAM 은 3924MB. heap cap 합이 RAM 을 172MB 초과한다. 게다가 같은 서버에 PostgreSQL(~400MB) + gunicorn 4워커(~600MB) + 가끔 Playwright(~300MB) 도 돈다.
왜 지금까지 안 터졌나
--max-old-space-size 는 V8 heap 의 천장값이지 할당량이 아니다.
실측 사용량은 프로세스당 ~100MB 라 평소엔 전혀 문제 없었다.
위험한 건 스파이크 순간이다. 메모리 누수나 큰 SSR 요청이 동시에 여러 프로세스에서 터지면 각자 천장(896MB)까지 자라려 하고 → 물리 RAM 초과 → Linux OOM killer 가 프로세스를 강제 종료 → 서비스 다운. 천장을 RAM 보다 높게 잡은 것 자체가 시한폭탄.
재산정 — Node / 비-Node 예산 분리
먼저 비-Node 가 쓰는 양을 빼고 Node 가용량을 계산:
전체 RAM 3924MB
- PostgreSQL ~400MB
- gunicorn 4워커 ~600MB
- OS ~400MB
- Playwright ~300MB (간헐)
─────────────────────────
Node 가용 ~2200MB
heap cap 합을 2.2GB 안쪽으로 재배분 (앱별 무게 반영):
# 수정 후
riel_agent: 2 × 512MB = 1024MB (블로그 SSR / 어드민 — 가장 무거움)
riel_chat: 2 × 384MB = 768MB (채팅 — 상대적으로 가벼움)
riel_secret: 1 × 384MB = 384MB
─────────────────────────────
합계 2176MB < 2200MB ✓
max_memory_restart (PM2 레벨 재시작 트리거) 도 heap cap 보다 약간 위로 맞췄다.
RSS = heap + 비-heap 이므로 heap 512 → restart 640M. true OOM 전에 PM2 가 먼저 재시작.
함정 — reload 로는 node_args 가 안 바뀐다
pm2 reload 했는데 --max-old-space-size 가 안 바뀌었다.
node_args 는 프로세스 spawn 시점의 인자라 graceful reload 로는 적용 안 된다.
# reload 로는 부족 pm2 reload ecosystem.config.js # node_args 안 바뀜완전 재시작 필요
pm2 delete ecosystem.config.js pm2 start ecosystem.config.js pm2 save # 재부팅 후에도 유지
적용 확인은 pm2 describe 의 interpreter args 로:
$ pm2 describe riel_agent | grep 'interpreter args'
│ interpreter args │ --max-old-space-size=512 │ ✓
교훈
- heap cap 합 < 물리 RAM 은 불변 규칙이다. 단일 프로세스 천장만 보지 말고 (인스턴스 수 × 천장) 의 총합을 RAM 과 비교. 한 서버에 DB/백엔드가 같이 있으면 그것도 차감.
- heap cap 은 할당이 아니라 천장. 평소 사용량이 낮아도 천장이 RAM 초과면 스파이크 시 OOM. "지금 안 터지니 괜찮다" 가 가장 위험한 판단.
- node_args 변경은 reload 가 아니라 restart. graceful reload 는 워커만 재생성하지 node 인터프리터 인자를 새로 안 준다. delete + start 후 pm2 save.
- max_memory_restart 를 heap cap 보다 약간 위에. PM2 가 OOM killer 보다 먼저 깨끗하게 재시작하도록. RSS 는 heap 보다 크다는 걸 감안.
태그
📨 박주니에게 한마디
스팸·악성 메시지 방지를 위해 구글 로그인 후 메시지를 보낼 수 있어요. 비공개로 전달되며, 운영자 외에는 볼 수 없습니다.
Google 로그인 후 메시지 남기기