16강 / 20강
메모리가 모자랄 때 — 스왑과 OOM Killer
free 를 제대로 읽고, 워커 메모리를 계산한다
새벽 3시에 알림이 옵니다. "쇼핑몰이 죽었어요."
로그를 보니 이렇게 적혀 있습니다.
kernel: Out of memory: Killed process 1046 (gunicorn) total-vm:1214MB, anon-rss:892MB
커널이 여러분의 앱을 죽였습니다. 왜 그랬고, 어떻게 막을까요?
먼저 free를 제대로 읽는 법
초보 관리자가 가장 많이 하는 오해가 여기 있습니다. "free가 300MB밖에 안 남았어요! 큰일이에요!"
이 다섯 칸의 뜻을 정확히 알아야 합니다.
bash
브라우저에서 실행
| 칸 | 뜻 | 봐야 하나? |
|---|---|---|
total | 전체 메모리 | 기준값 |
used | 프로세스들이 실제로 쓰는 양 | △ |
free | 아무도 안 쓰는 순수 빈 공간 | ✗ 보지 마세요 |
buff/cache | 커널이 빌려 쓰고 있는 파일 캐시 | △ |
available | 새 프로그램이 당장 쓸 수 있는 양 | ✓ 이것만 보세요 |
워커 메모리를 계산합시다
쇼핑몰 서버에서 메모리를 먹는 건 대부분 gunicorn 워커입니다. 워커 하나가 얼마나 쓰는지 재고, 몇 개까지 띄울 수 있는지 계산해 봅시다.
RES 칸이 워커 하나가 실제로 잡은 물리 메모리입니다.
bash
브라우저에서 실행
워커 수의 상한은 CPU가 아니라 메모리가 정할 때가 많습니다.
Python
브라우저에서 실행
메모리가 모자라면 벌어지는 일, 순서대로
- 파일 캐시를 버린다 — 조용히 일어남. 디스크 읽기가 조금 늘어난다
- 스왑으로 내린다 — 안 쓰는 페이지를 디스크로. 여기서부터 느려진다
- 스왑을 계속 들락날락(thrashing) — 응답시간이 무너진다.
wa와sy가 치솟는다 - OOM Killer 발동 — 커널이 프로세스 하나를 죽인다
메모리를 14GB 요구하면 지표가 어떻게 되는지 봅니다.
bash
브라우저에서 실행
si/so가 계속 0이 아니면 스왑 중입니다.
bash
브라우저에서 실행
실무에서 새벽 장애를 만나면 가장 먼저 치는 명령입니다.
bash
브라우저에서 실행
대응 방법 — 순서대로
| 방법 | 비용 | 언제 |
|---|---|---|
| 워커 수를 줄인다 | 무료·즉시 | 지금 당장 살려야 할 때. 처리량은 줄지만 죽지는 않는다 |
워커에 max-requests 를 건다 | 무료 | 메모리 누수가 의심될 때. 주기적으로 워커를 재시작 |
gunicorn preload | 무료 | 워커들이 라이브러리를 공유하게 해 실사용량을 줄인다 |
| 메모리 증설 | 돈·재부팅 | 슬롯이 비어 있다면 가장 확실한 해법 |
| 코드에서 메모리 줄이기 | 시간 | 한 번에 다 읽지 말고 스트리밍으로 |
| 스왑을 늘린다 | 무료 | 근본 해결이 아니다. 죽는 대신 느려질 뿐 |
기본값 60은 데스크톱 기준입니다. 서버는 보통 10~20으로 낮춥니다.
bash
브라우저에서 실행
🤔 확인 문제
free -h 결과가 total 16Gi, used 9.2Gi, free 0.3Gi, buff/cache 6.5Gi, available 6.8Gi 입니다. 이 서버는 위험한가요?해설
free가 작은 건 커널이 남는 메모리를 파일 캐시로 잘 활용하고 있다는 뜻입니다. 프로그램이 메모리를 달라고 하면 캐시를 즉시 비워서 줍니다. 봐야 할 건 available이고, 6.8Gi = 42%면 여유롭습니다.🤔 확인 문제
워커 하나가 340MiB를 쓰는 16GiB(16,384MiB) 서버에서, 워커를 48개로 늘리면 어떻게 될까요?
해설 메모리를 넘기면 스왑에 빠지고, 스왑에 빠지면 응답이 수십 배 느려지고(7·9강), 그래도 부족하면 OOM Killer가 가장 큰 프로세스를 죽입니다. 참고로 45개(16,080MiB)까지는 물리 메모리 안에 들어가지만 여유가 2%도 안 남아 이미 스왑이 시작됩니다 — 위젯에서 직접 확인해 보세요. 워커 수의 상한은 CPU가 아니라 메모리가 정할 때가 많습니다.