Linux 메모리 관리: 스왑, OOM 킬러 및 C그룹
12분 소요 - 2026년 5월 31일

데이터베이스, 웹 서버 및 멀티테넌트 VPS 호스트에 대한 구성 예제와 함께 Linux 스왑, OOM 킬러 및 cgroups가 함께 작동하는 방식.
리눅스 메모리 관리 설명: 스왑, OOM 킬러, cgroups
리눅스는 대부분의 운영 체제와 달리 메모리를 다르게 처리합니다. 높은 RAM 사용량이 항상 문제인 것은 아닙니다. 커널은 디스크 읽기 속도를 높이기 위해 사용 가능한 메모리를 캐싱에 적극적으로 활용하기 때문입니다. 하지만 실제 메모리 부하가 가중되면 스왑(swap), OOM 킬러(OOM killer), cgroups라는 세 가지 메커니즘이 작동합니다. 각 메커니즘의 작동 방식과 구성 방법을 이해하는 것은 부하가 가해졌을 때 점진적으로 성능이 저하되는 서버와 경고 없이 다운되는 서버의 차이를 결정짓습니다.
리눅스의 메모리 페이지 관리 방식
모든 프로세스는 64비트 시스템에서 최대 128TB에 달하는 자체 가상 주소 공간에서 실행됩니다. 커널은 페이지 테이블을 통해 이러한 가상 주소를 물리적 RAM에 매핑하며, 최근 조회 내역은 TLB(Translation Lookaside Buffer)에 캐싱됩니다. TLB 히트(hit)는 약 1나노초가 소요되는 반면, 미스(miss)는 20~100나노초가 소요되며, 이는 데이터베이스와 같은 메모리 집약적 워크로드에서 누적됩니다.
물리적 메모리는 4KB 페이지로 나뉘며, 커널은 이를 두 가지 범주로 구분합니다:
- 파일 기반 페이지 — 디스크의 파일에 연결된 페이지입니다. 커널은 스왑을 사용하지 않고도 깨끗한 페이지는 버리거나 더티 페이지는 플러시할 수 있습니다.
- 익명 페이지 — 백업 파일이 없는 힙 및 스택 메모리입니다. 커널이 이 페이지를 해제하기 전에 스왑 영역에 기록해야 합니다.
메모리 수요가 높은 서버에서 익명 페이지의 비율이 높으면 스왑이 조기에 개입하게 됩니다. si (swap in) 및 so (스왑 아웃) 열을 vmstat 1 — 0이 아닌 값이 지속적으로 나타나는 것은 시스템에 부하가 걸리고 있다는 첫 번째 경고 신호입니다.
모니터링 시에는 적절한 도구를 사용하십시오:
| 도구 | 가장 적합한 용도 | 주요 지표 |
|---|---|---|
free -h | 전체 시스템의 빠른 현황 파악 | available 열 |
vmstat 1 | 실시간 스왑 및 I/O 모니터링 | si, so |
htop | 프로세스별 대화형 보기 | 메모리 바, 프로세스 목록 |
smem | 정확한 프로세스별 사용량 | USS(고유 세트 크기) |
/proc/meminfo | 커널 수준 세부 정보 | MemAvailable, Dirty, Slab |
흔히 저지르는 실수: free 열을 free -h 열을 보고 당황하는 것입니다. 중요한 것은 available 열이 중요한 것입니다. 여기에는 커널이 필요에 따라 캐시에서 회수할 수 있는 메모리가 포함됩니다. 사용 가능한 메모리는 5GB인데 여유 메모리는 512MB밖에 표시되지 않는다고 해서 서버에 문제가 있는 것은 아닙니다.
메모리가 임계값 아래로 떨어지면 커널의 kswapd 데몬이 백그라운드에서 페이지를 회수하기 시작합니다. 이것만으로는 부족하면 커널은 직접 회수 모드로 전환되어 페이지가 해제될 때까지 프로세스를 차단합니다. 이것이 바로 지연 시간 급증이 발생하는 원인입니다. MemAvailable 전체 RAM의 10~15% 미만으로 떨어질 때 경보를 설정하여 대응할 시간을 확보하십시오.
스왑 구성
스왑(Swap)은 RAM이 가득 찼을 때 커널이 비활성 익명 페이지를 이동시키는 디스크 영역(파티션 또는 파일)입니다. 속도 차이는 상당합니다. DDR4 RAM의 지연 시간은 대략 100ns인 반면, NVMe SSD는 약 100,000ns, SATA SSD는 500,000ns에 가깝습니다. 스왑은 추가 RAM이 아닌 안전 버퍼입니다. 지속적으로 스왑에 의존하는 서버는 더 많은 스왑 공간으로도 해결할 수 없는 메모리 문제를 가지고 있는 것입니다.
파티션 대신 스왑 파일을 사용하십시오. 크기를 조정하기 더 쉽고 파티션을 다시 나눌 필요가 없습니다.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile파일을 /etc/fstab 재부팅 후에도 데이터가 유지되도록 하십시오. chmod 600 단계는 필수입니다. RAM에서 페이징된 모든 데이터는 스왑에서 읽을 수 있으므로, 해당 파일은 일반 사용자가 읽을 수 없도록 설정해야 합니다.
스왑을 생성한 후 vm.swappiness를 조정하십시오. 기본값인 60은 공격적인 설정입니다. 대부분의 호스팅 워크로드에서는 커널이 RAM을 우선적으로 사용하고 스왑은 최후의 수단으로만 사용하도록 설정하는 것이 좋습니다:
| 서버 역할 | vm.swappiness | vm.vfs_cache_pressure |
|---|---|---|
| 일반 웹 서버 | 10–20 | 50 |
| 데이터베이스 (MySQL/PostgreSQL) | 1–5 | 50 |
| 기본값 (대부분의 배포판) | 60 | 100 |
스왑 크기 설정: 가끔 트래픽 급증이 발생하는 2GB VPS의 경우 1–2GB면 충분합니다. 8GB 이상의 시스템에서는 일반적으로 고정된 2–4GB 스왑으로 충분합니다. 목표는 커널에 콜드 페이지(cold pages)를 위한 안전 밸브를 제공하는 것이지, 총 주소 지정 가능 메모리를 확장하는 것이 아닙니다.
RAM이 제한적이지만 CPU 성능이 충분한 서버에서는 zram을 사용하여 메모리 내에 압축된 스왑 영역을 생성함으로써 디스크 I/O를 완전히 피할 수 있습니다. 테넌트 간에 NVMe를 공유하는 멀티 테넌트 VPS 호스트에서는 이를 고려해 볼 가치가 있습니다. 스왑이 데이터베이스 파일과 동일한 장치에 위치할 경우 I/O 경합을 주의해야 합니다. 과도한 스왑 활동과 높은 처리량의 디스크 쓰기 작업은 서로 잘 공존하지 않습니다.
OOM 킬러
커널이 RAM과 스왑 공간을 모두 소진하고 다른 방법으로 충분한 메모리를 확보할 수 없을 때, OOM 킬러가 작동합니다. OOM 킬러는 oom_badness() 다음 함수를 사용하여 프로세스에 점수를 매깁니다:
points = (rss_anon + rss_file + rss_shmem + swapents + pgtables_pages) + (oom_score_adj × totalpages / 1000)점수가 가장 높은 프로세스가 종료됩니다. 이 공식은 메모리를 많이 사용하는 프로세스를 우선적으로 처리하며, 커널은 지난 5초 이내에 해당 프로세스가 이미 종료되었는지 확인하여 여러 프로세스가 연달아 종료되는 것을 방지합니다.
로그에는 두 가지 유형의 OOM 이벤트가 나타납니다:
- 전역 OOM — 시스템 전체의 RAM과 스왑 공간이 부족합니다. 로그 앞부분에
Out of memory: - Cgroup OOM — 컨테이너나 서비스가
memory.max한도에 도달했습니다. 로그 앞부분에Memory cgroup out of memory:
과거 OOM 이벤트를 확인하려면:
dmesg -T | grep -i "out of memory"
journalctl -k --grep="oom"OOM 로그의 order 필드를 주의 깊게 살펴보세요. 값이 0보다 크면 메모리가 완전히 고갈된 것이 아니라 조각난 상태임을 의미합니다. 즉, 사용 가능한 여유 메모리가 있음에도 커널이 충분한 연속된 페이지를 찾지 못한 것입니다.
다음 설정을 조정하여 OOM 킬러가 어떤 프로세스를 대상으로 할지 제어할 수 있습니다 /proc/<pid>/oom_score_adj. 범위는 -1000(절대 종료하지 않음)부터 +1000(우선 종료)까지입니다. systemd로 관리되는 서비스의 경우, 유닛 파일에서 이 값을 영구적으로 설정하십시오:
[Service]
OOMScoreAdjust=-1000OOM 동작을 조정하기 위한 추가 sysctl 매개변수:
| 매개변수 | 값 | 효과 |
|---|---|---|
vm.overcommit_memory | 0 | 기본 휴리스틱 오버커밋 모드 |
vm.overcommit_memory | 2 | 엄격 모드; 할당량이 RAM × overcommit_ratio + swap을 초과하지 못하도록 방지 |
vm.panic_on_oom | 1 | 프로세스를 강제 종료하는 대신 재부팅합니다 |
vm.oom_kill_allocating_task | 1 | 가장 많은 메모리를 사용하는 프로세스 대신 OOM을 유발한 프로세스를 종료합니다 |
사전 모니터링을 위해 다음을 확인하십시오 /proc/pressure/memory (Pressure Stall Information, 커널 4.20부터 지원됨). some avg10 값을 주시하십시오: 5% 미만이면 정상이며, 20% 이상으로 지속되면 OOM 이벤트가 발생할 가능성이 높습니다. 카운터 값이 상승하면 allocstall 카운터는 /proc/vmstat 의 카운터가 증가하는 것도 또 다른 초기 신호입니다. 이 카운터는 OOM 종료 직전에 흔히 발생하는 직접 회수 스톨을 집계합니다. systemd-oomd 또는 earlyoom 와 같은 도구는 커널의 OOM 킬러가 작동하기 전에 PSI 임계값에 따라 조치를 취할 수 있습니다.
cgroups 및 메모리 제한
컨트롤 그룹(cgroups)을 사용하면 프로세스를 그룹으로 구성하고 엄격한 리소스 제한을 적용할 수 있습니다. 리눅스 2.6.24에 도입된 Cgroups는 Docker, Podman, Kubernetes, LXC를 포함한 컨테이너 런타임의 기반이 됩니다. 커널은 익명 메모리, 파일 기반 페이지, 커널 객체를 포함하여 Cgroup별 메모리 사용량을 추적합니다. Cgroup이 제한에 도달하면 커널은 해당 그룹 내의 메모리를 회수하거나 Cgroup 범위 내의 OOM(Out of Memory) 킬을 트리거합니다.
Cgroup v1과 v2의 주요 차이점은 구조에 있습니다. v1은 각 컨트롤러(메모리, CPU, I/O)를 /sys/fs/cgroup/<controller>/에 별도로 마운트하여 자원 추적의 일관성을 저해합니다. V2는 /sys/fs/cgroup/를 사용합니다. Kubernetes는 버전 1.25부터 v2를 기본값으로 전환했으며, 1.31에서 v1 지원을 중단했습니다.
시스템에서 사용하는 버전을 확인하려면:
stat -fc %T /sys/fs/cgroup/cgroup2fs 는 v2를 의미합니다; tmpfs 일반적으로 v1을 의미합니다.
| 기능 | Cgroup v1 | Cgroup v2 |
|---|---|---|
| 계층 구조 | 컨트롤러별로 여러 개 | 단일, 통합 |
| 하드 메모리 제한 | memory.limit_in_bytes | memory.max |
| 소프트 메모리 제한 | memory.soft_limit_in_bytes | memory.high (스로틀링) |
| 사용량 추적 | memory.usage_in_bytes | memory.current |
| 부하 지표 | 제한됨 | PSI 통합 |
cgroup v2의 주요 메모리 제어 기능:
| 매개변수 | 유형 | 설명 |
|---|---|---|
memory.max | 하드 리미트 | 이 값을 초과하면 OOM 킬러가 작동합니다 |
memory.high | 소프트 제한 | 할당을 제한하고 하드 제한에 도달하기 전에 회수를 트리거합니다 |
memory.low | 소프트 보호 | 이 임계값 미만의 메모리는 가장 마지막에 회수됩니다 |
memory.min | 하드 보호 | 이 수준 이하의 메모리는 절대 회수되지 않습니다 |
memory.swap.max | 스왑 제한 | 이 cgroup에 대한 스왑을 비활성화하려면 0으로 설정하십시오 |
memory.oom.group | 부울 | 이 옵션이 활성화되면, OOM 발생 시 cgroup 내의 모든 프로세스를 함께 종료합니다 |
실용적인 규칙: memory.high 하한선보다 약 10~20% 낮은 memory.max 설정하여 하드 리미트에 도달하기 전에 커널이 메모리를 회수할 여지를 확보하십시오. 크기를 조정할 때 memory.max, cgroup 메모리 총량에 포함되는 페이지 캐시를 고려하여 애플리케이션의 최대 사용량보다 20~30% 더 크게 설정하십시오.
cgroup 파일 시스템에 직접 기록하는 대신 systemd를 통해 cgroup을 관리하십시오. MemoryMax=, MemoryHigh=와 MemoryMin= 와 같은 유닛 파일 지시어를 사용하여 지속적 제한을 설정하십시오. 빠른 테스트를 위해:
systemd-run --scope -p MemoryMax=512M <command>웹 서버 워커 풀의 경우, memory.oom.group=1 를 설정하면 한 워커가 한도를 초과할 때 깔끔하게 종료되어 고아 프로세스가 남지 않습니다. 데이터베이스 엔진의 경우, memory.min 설정하면 시스템 전체의 부하로 인해 버퍼 풀이 회수되는 것을 방지할 수 있습니다.
서버 역할별 메모리 구성
올바른 메모리 설정은 서버의 용도에 따라 달라집니다. 데이터베이스 서버와 PHP 웹 서버에 동일한 구성을 적용하면 둘 중 하나에 문제가 발생할 수 있습니다.
| 서버 역할 | vm.swappiness | OOM 전략 | Cgroup 정책 |
|---|---|---|---|
| 데이터베이스 | 1–5 | 보호 (OOMScoreAdjust=-900) | 사용 memory.min 버퍼 풀 보호 |
| 웹/애플리케이션 서버 | 10–20 | 기본값 | 다음 설정을 통해 워커 풀당 상한선 memory.max |
| 백그라운드 워커 | 60 | 중단 가능 (OOMScoreAdjust=+200) | 다음에 의한 스로틀링 memory.high |
| 다중 테넌트 VPS | 60 (zram 사용 시) | 기본값 | 테넌트별 하드 격리 memory.max |
MySQL 및 PostgreSQL의 경우, 사용 가능한 RAM의 50~70%를 할당하고 innodb_buffer_pool_size, 지연 시간 급증을 줄이기 위해 Transparent Huge Pages를 비활성화하고, OOMScoreAdjust=-900 로 보호하십시오.
PHP-FPM의 경우, 실제 메모리 사용량을 기준으로 워커 풀 크기를 조정하십시오. 각 워커는 일반적으로 30~100MB를 사용합니다. 할당된 RAM을 평균 워커 크기로 나누어 안전한 pm.max_children 값을 구하십시오. cgroups에서 memory.max 를 사용하여 풀 크기를 제한하십시오.
쓰기 작업이 많은 워크로드의 경우 vm.dirty_ratio 을 약 10%로, vm.dirty_background_ratio 3%로 설정하십시오. 이렇게 하면 더티 페이지를 더 자주 플러시하여 대규모 I/O 지연을 방지할 수 있습니다.
매개변수를 /etc/sysctl.d/90-memory.conf에 저장하여 커널 튜닝을 영구적으로 적용하십시오. 런타임에 적용된 설정은 재부팅 시 사라집니다.
역할별 권장 값 요약:
| 매개변수 | 웹/애플리케이션 서버 | 데이터베이스 서버 |
|---|---|---|
vm.swappiness | 10–20 | 1–5 |
vm.vfs_cache_pressure | 50 | 50 |
vm.dirty_ratio | 15% | 10% |
vm.min_free_kbytes | 65536 | 65536 |
| OOM 보호 | 기본 | OOMScoreAdjust=-1000 |
고밀도 워크로드를 실행 중이며 이러한 정책을 적절히 적용할 수 있는 여유 용량을 갖춘 서버가 필요하다면, FDC의 전용 서버를 고려해 볼 만합니다.

Linux 메모리 관리: 스왑, OOM 킬러 및 C그룹
데이터베이스, 웹 서버 및 멀티테넌트 VPS 호스트에 대한 구성 예제와 함께 Linux 스왑, OOM 킬러 및 cgroups가 함께 작동하는 방식.
12분 소요 - 2026년 5월 31일
Prometheus 및 node_exporter 설정 가이드
15분 소요 - 2026년 5월 29일

질문이 있거나 맞춤형 솔루션이 필요하신가요?
유연한 옵션
글로벌 도달 범위
즉시 배포
유연한 옵션
글로벌 도달 범위
즉시 배포