
가상 메모리와 스왑: 책상이 작을 때 쓰는 속임수
8GB 램으로 어떻게 10GB짜리 게임을 돌릴까요? OS가 몰래 쓰는 하드디스크의 일부, 스왑(Swap)과 가상 메모리의 원리.

8GB 램으로 어떻게 10GB짜리 게임을 돌릴까요? OS가 몰래 쓰는 하드디스크의 일부, 스왑(Swap)과 가상 메모리의 원리.
맥북 배터리는 왜 오래 갈까? 서버 비용을 줄이려면 AWS Graviton을 써야 할까? 복잡함(CISC)과 단순함(RISC)의 철학적 차이를 정리해봤습니다.

AI 시대의 금광, 엔비디아 GPU. 도대체 게임용 그래픽카드로 왜 AI를 돌리는 걸까? 단순 노동자(CUDA)와 행렬 계산 천재(Tensor)의 차이로 파헤쳐봤습니다.

빠른 SSD를 샀는데 왜 느릴까요? 1차선 시골길(SATA)과 16차선 고속도로(NVMe). 인터페이스가 성능의 병목이 되는 이유.

LP판과 USB. 물리적으로 회전하는 판(Disc)이 왜 느릴 수밖에 없는지, 그리고 SSD가 어떻게 서버의 처리량을 100배로 만들었는지 파헤쳐봤습니다.

제 램은 8GB입니다. 근데 '배틀그라운드'랑 '크롬 탭 30개'랑 '유튜브'를 다 켜도 컴퓨터가 안 꺼집니다. 합치면 분명 12GB가 넘을 텐데 말이죠. 처음엔 진짜 의아했습니다. 물리적으로 존재하는 램이 8GB인데, 어떻게 12GB를 쓰는 프로그램들이 돌아갈까요?
답은 간단했습니다. 운영체제(OS)가 거짓말을 하고 있었습니다. "걱정 마, 우리 집 램 무한대야. 마음껏 써."
이게 바로 가상 메모리(Virtual Memory)입니다.
제가 이걸 공부한 계기는 AWS EC2 t2.micro 인스턴스 때문이었습니다. 램이 1GB밖에 안 되는 이 인스턴스로 Node.js 서버를 돌리는데, 자꾸 서버가 죽는 겁니다. 에러 로그를 보니 OOM killed process 라는 무시무시한 메시지가 떠 있더라고요.
검색해보니 "Swap 메모리를 설정하세요"라는 답변이 수두룩했습니다. 그래서 설정했는데, 서버는 안 죽는데 응답 속도가 거북이처럼 느려졌습니다. 뭔가 이상했습니다. "스왑을 쓰면 메모리가 늘어나는 거 아닌가? 왜 느려지지?"
이 의문이 저를 가상 메모리와 스왑의 세계로 끌고 들어갔습니다.
제가 처음 이해하려고 했을 때 가장 헷갈렸던 게 이 부분입니다.
처음엔 이 둘이 같은 거라고 생각했습니다. 틀렸습니다. 가상 메모리는 개념이고, 스왑은 구현 방법 중 하나였습니다.
이것도 완전히 오해했습니다. 스왑은 메모리를 늘려주는 게 아니라, 메모리 부족으로 죽는 걸 막아주는 대신 속도를 포기하는 거였습니다.
RAM이랑 SSD는 1,000배 차이입니다. RAM과 HDD는 1,000,000배 차이입니다.
이것도 틀렸습니다. 스왑을 크게 잡는다고 좋은 게 아닙니다. 스왑이 실제로 사용되는 순간, 시스템은 느려집니다. 스왑은 "급한 불 끄기" 용도입니다. 근본 해결책은 RAM을 늘리는 것입니다.
그러다가 이 비유를 듣고 무릎을 쳤습니다.
여러분이 공부하려고 도서관에 갔습니다.
책상이 좁은데 책 10권이 필요하다면?
OS는 학생에게 "여기 10권 펼칠 수 있는 가짜 책상(가상 메모리)이 있어"라고 속입니다. 그리고 실제로는 학생이 지금 당장 보는 책 1권만 진짜 책상(RAM)에 둡니다.
나머지 9권? 서가에 꽂혀 있습니다. 학생이 다른 책을 보려고 하면, OS는 빛의 속도로 움직입니다.
이 과정이 스와핑(Swapping)입니다.
호텔에 방이 100개 있는데, 예약을 150개 받습니다. "어차피 다 안 올 거야. 일부는 노쇼할 테니까."
이게 바로 가상 메모리입니다. OS는 프로세스에게 각각 "4GB씩 쓸 수 있어"라고 약속합니다. 근데 실제 RAM은 8GB뿐입니다. OS는 계산합니다. "어차피 다 안 쓸 거야. 일부는 idle 상태일 테니까."
그런데 만약 손님들이 진짜 다 오면? 호텔은 패닉에 빠집니다. OS도 마찬가지입니다. 이게 바로 스레싱(Thrashing)입니다.
여러분이 창고에서 물건을 수리하는 기술자입니다.
작업대가 작으면? 물건을 자꾸 바꿔가며 작업해야 합니다. 이 과정이 느립니다. 창고까지 왕복하는 시간이 오래 걸리기 때문입니다.
이 비유들이 제 머릿속을 확 정리해줬습니다. 결국 가상 메모리는 환상이고, 스왑은 환상을 유지하기 위해 치르는 대가였습니다.
이제 좀 더 깊이 들어가 봅니다.
프로세스는 가상 주소(Virtual Address)를 봅니다. 예를 들어, 프로그램이 "메모리 주소 0x1000에 있는 값을 읽어줘"라고 요청합니다.
그런데 이 주소는 가짜입니다. 진짜 RAM의 주소가 아닙니다.
OS는 페이지 테이블(Page Table)을 사용해서 가상 주소를 물리 주소(Physical Address)로 변환합니다.
가상 주소 0x1000 → 페이지 테이블 조회 → 물리 주소 0x3000
이 과정이 MMU(Memory Management Unit)라는 하드웨어가 처리합니다.
메모리는 페이지(Page)라는 단위로 나뉩니다. 보통 4KB입니다.
페이지 테이블은 이런 식으로 생겼습니다.
| 가상 페이지 번호 | 물리 페이지 번호 | 상태 |
|---|---|---|
| 0 | 3 | RAM에 있음 |
| 1 | - | Disk에 있음 (Swap) |
| 2 | 7 | RAM에 있음 |
| 3 | - | Disk에 있음 (Swap) |
프로세스가 가상 페이지 1번을 읽으려고 하면? 페이지 테이블을 보니 "Disk에 있음"이라고 써 있습니다. 이게 바로 페이지 폴트(Page Fault)입니다.
페이지 폴트가 발생하면 OS는 이렇게 행동합니다.
이 과정이 엄청 느립니다. Disk I/O는 RAM 접근보다 수천 배 느리기 때문입니다.
OS는 필요할 때만(On Demand) 페이지를 RAM에 올립니다. 프로그램을 실행할 때 전체를 RAM에 올리는 게 아니라, 지금 당장 필요한 부분만 올립니다.
예를 들어, 크롬 브라우저를 실행하면 전체 프로그램이 100MB라고 칩시다. 그런데 실제로 지금 사용하는 건 10MB뿐입니다. OS는 10MB만 RAM에 올립니다. 나머지 90MB는 Disk에 그대로 둡니다.
사용자가 새로운 기능을 쓰려고 하면? 그때 가서 필요한 페이지를 RAM에 올립니다. 이게 Demand Paging입니다.
RAM이 꽉 찼는데 새로운 페이지를 올려야 한다면? 기존 페이지 중 하나를 쫓아내야 합니다. 이때 어떤 페이지를 쫓아낼지 결정하는 게 페이지 교체 알고리즘입니다.
대표적인 알고리즘들:
리눅스는 주로 LRU 변형을 사용합니다.
이제 스왑 공간에 대해 정리해봅니다.
스왑 공간을 만드는 방법은 두 가지입니다.
디스크의 일부를 완전히 스왑 전용으로 할당합니다. 포맷도 스왑 전용 포맷으로 합니다.
장점:
단점:
일반 파일 시스템 안에 큰 파일 하나를 만들어서 스왑으로 씁니다.
장점:
단점:
요즘엔 Swap File을 더 많이 씁니다. 클라우드 환경에서는 특히 그렇습니다.
옛날 규칙: "RAM의 2배" 요즘 규칙: "상황에 따라 다름"
제가 받아들인 원칙은 이렇습니다.
제가 했던 설정입니다.
# 1. 스왑 파일 생성 (1GB)
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
# 2. 권한 설정 (보안상 중요)
sudo chmod 600 /swapfile
# 3. 스왑 포맷
sudo mkswap /swapfile
# 4. 스왑 활성화
sudo swapon /swapfile
# 5. 현재 상태 확인
free -h
출력:
total used free shared buff/cache available
Mem: 1.0G 500M 200M 10M 300M 400M
Swap: 1.0G 0B 1.0G
Swappiness는 "언제부터 스왑을 쓸까?"를 결정하는 값입니다. 0~100 사이입니다.
제가 쓰는 값은 10입니다. "정말 급할 때만 스왑을 써라"는 의미입니다.
# 현재 값 확인
cat /proc/sys/vm/swappiness
# 임시 변경 (재부팅하면 사라짐)
sudo sysctl vm.swappiness=10
# 영구 변경
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
# 스왑 사용량 확인
free -h
# 스왑 사용 중인 프로세스 확인
for file in /proc/*/status ; do
awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file
done | sort -k 2 -n -r | head -10
이 스크립트는 "어떤 프로세스가 스왑을 많이 쓰는지"를 보여줍니다. 프로덕션 서버에서 이걸 돌려봤더니, Node.js 프로세스가 스왑을 500MB나 쓰고 있었습니다. 그 순간 "아, 이래서 느렸구나"라고 이해했습니다.
Docker 컨테이너에 메모리 제한을 걸 수 있습니다.
# 메모리 512MB로 제한
docker run -m 512m my-app
# 메모리 + 스왑 제한 (total 1GB)
docker run -m 512m --memory-swap 1g my-app
--memory-swap은 메모리 + 스왑 합계입니다. 위 예시는 "메모리 512MB + 스왑 512MB"를 의미합니다.
만약 --memory-swap을 설정하지 않으면? Docker는 메모리의 2배를 스왑으로 자동 설정합니다.
Linux에는 OOM Killer라는 무시무시한 기능이 있습니다. "Out of Memory"가 발생하면, OS는 프로세스 중 하나를 강제로 죽입니다.
어떤 프로세스를 죽일까요? OOM Score가 높은 프로세스부터 죽입니다.
# 모든 프로세스의 OOM Score 확인
ps -eo pid,comm,oom_score | sort -k3 -n -r | head -10
출력:
PID COMMAND OOM_SCORE
1234 node 800
5678 postgres 300
9012 nginx 50
Node.js 프로세스가 800점으로 1등입니다. 메모리 부족 시 제일 먼저 죽습니다.
OOM Score를 낮추려면?
# PID 1234의 OOM Score Adjust 값을 -500으로 설정
echo -500 | sudo tee /proc/1234/oom_score_adj
음수 값은 "이 프로세스는 중요하니까 죽이지 마"라는 의미입니다.
스레싱은 제가 직접 경험한 악몽입니다.
페이지 폴트가 너무 자주 일어나는 상태입니다. OS가 Swap In/Out하느라 정작 실제 작업은 못 합니다.
비유하자면, 도서관 사서가 책을 나르느라 정작 학생들은 책을 못 읽는 상황입니다.
RAM이 부족한데 프로세스가 많을 때 발생합니다.
예를 들어:
RAM이 모자라니 OS는 스왑을 씁니다. 그런데 프로세스들이 모두 동시에 활동하면? OS는 쉴 새 없이 Swap In/Out을 반복합니다.
제 서버가 이 상태였을 때, SSH 접속도 1분씩 걸렸습니다.
근본 해결책: RAM을 늘리거나, 프로세스를 줄입니다.
임시 해결책:
| 구분 | 기본 상태 | 가상 메모리 & 스왑 |
|---|---|---|
| 비유 | 책상 크기만큼만 책을 폄 | 책장을 책상인 척 씀 |
| 장점 | 엄청 빠름 (All RAM) | 용량 한계 극복 가능 |
| 단점 | 비쌈, 용량 부족 시 뻗음 | 느림 (디스크 속도) |
| 결과 | 프로그램 강제 종료 (OOM) | 버벅임 (Thrashing) |
저는 이걸 이렇게 이해했습니다.
가상 메모리는 OS의 환상입니다. "우리 집 메모리 무한대야"라고 속입니다. 스왑은 환상을 유지하기 위해 디스크를 몰래 씁니다. 대가는 속도입니다.
"스왑 메모리 좀 늘려서 해결하죠?"
이 말은 "일단 급한 불만 끄고, 속도 느려지는 건 나중에 생각하시죠?"라는 말과 같습니다.
진짜 해결책은 RAM을 늘리는 것입니다. 스왑은 보험입니다. 보험은 쓰지 않는 게 최선입니다.