"램 8기가인데 배그 돌아가나요?"
제 램은 8GB입니다. 근데 '배틀그라운드'랑 '크롬 탭 30개'랑 '유튜브'를 다 켜도 컴퓨터가 안 꺼집니다. 합치면 분명 12GB가 넘을 텐데 말이죠. 처음엔 진짜 의아했습니다. 물리적으로 존재하는 램이 8GB인데, 어떻게 12GB를 쓰는 프로그램들이 돌아갈까요?
답은 간단했습니다. 운영체제(OS)가 거짓말을 하고 있었습니다. "걱정 마, 우리 집 램 무한대야. 마음껏 써."
이게 바로 가상 메모리(Virtual Memory)입니다.
1. 왜 가상 메모리를 공부했나?
제가 이걸 공부한 계기는 AWS EC2 t2.micro 인스턴스 때문이었습니다. 램이 1GB밖에 안 되는 이 인스턴스로 Node.js 서버를 돌리는데, 자꾸 서버가 죽는 겁니다. 에러 로그를 보니 OOM killed process 라는 무시무시한 메시지가 떠 있더라고요.
검색해보니 "Swap 메모리를 설정하세요"라는 답변이 수두룩했습니다. 그래서 설정했는데, 서버는 안 죽는데 응답 속도가 거북이처럼 느려졌습니다. 뭔가 이상했습니다. "스왑을 쓰면 메모리가 늘어나는 거 아닌가? 왜 느려지지?"
이 의문이 저를 가상 메모리와 스왑의 세계로 끌고 들어갔습니다.
2. 처음엔 뭐가 헷갈렸나?
제가 처음 이해하려고 했을 때 가장 헷갈렸던 게 이 부분입니다.
헷갈렸던 포인트 1 - "가상 메모리 = 스왑?"
처음엔 이 둘이 같은 거라고 생각했습니다. 틀렸습니다. 가상 메모리는 개념이고, 스왑은 구현 방법 중 하나였습니다.
- 가상 메모리: "우리 집 램 무한대야"라고 속이는 환상
- 스왑: 환상을 현실로 만들기 위해 하드디스크를 몰래 쓰는 기술
헷갈렸던 포인트 2 - "스왑 쓰면 메모리 늘어나는데 왜 느려?"
이것도 완전히 오해했습니다. 스왑은 메모리를 늘려주는 게 아니라, 메모리 부족으로 죽는 걸 막아주는 대신 속도를 포기하는 거였습니다.
- RAM 속도: 나노초(10⁻⁹초) 단위
- SSD 속도: 마이크로초(10⁻⁶초) 단위
- HDD 속도: 밀리초(10⁻³초) 단위
RAM이랑 SSD는 1,000배 차이입니다. RAM과 HDD는 1,000,000배 차이입니다.
헷갈렸던 포인트 3 - "그럼 스왑 크게 잡으면 되는 거 아냐?"
이것도 틀렸습니다. 스왑을 크게 잡는다고 좋은 게 아닙니다. 스왑이 실제로 사용되는 순간, 시스템은 느려집니다. 스왑은 "급한 불 끄기" 용도입니다. 근본 해결책은 RAM을 늘리는 것입니다.
3. 이해의 전환점 - 책상과 서랍 비유
그러다가 이 비유를 듣고 무릎을 쳤습니다.
비유 1 - 도서관의 작은 책상
여러분이 공부하려고 도서관에 갔습니다.
- 물리 메모리(RAM): 여러분 앞의 작은 책상. 책을 3권만 펼칠 수 있습니다.
- 하드디스크(HDD/SSD): 도서관의 거대한 서가. 책이 수만 권 있습니다.
- 프로세스(Program): 책 10권을 동시에 펴놓고 공부하고 싶은 학생.
책상이 좁은데 책 10권이 필요하다면?
OS는 학생에게 "여기 10권 펼칠 수 있는 가짜 책상(가상 메모리)이 있어"라고 속입니다. 그리고 실제로는 학생이 지금 당장 보는 책 1권만 진짜 책상(RAM)에 둡니다.
나머지 9권? 서가에 꽂혀 있습니다. 학생이 다른 책을 보려고 하면, OS는 빛의 속도로 움직입니다.
- 지금 책상에 있는 안 보는 책을 서가(HDD)로 치워버립니다. (Swap Out)
- 학생이 찾는 책을 서가에서 가져와 책상에 놓습니다. (Swap In)
이 과정이 스와핑(Swapping)입니다.
비유 2 - 호텔 오버부킹
호텔에 방이 100개 있는데, 예약을 150개 받습니다. "어차피 다 안 올 거야. 일부는 노쇼할 테니까."
이게 바로 가상 메모리입니다. OS는 프로세스에게 각각 "4GB씩 쓸 수 있어"라고 약속합니다. 근데 실제 RAM은 8GB뿐입니다. OS는 계산합니다. "어차피 다 안 쓸 거야. 일부는 idle 상태일 테니까."
그런데 만약 손님들이 진짜 다 오면? 호텔은 패닉에 빠집니다. OS도 마찬가지입니다. 이게 바로 스레싱(Thrashing)입니다.
비유 3 - 창고와 작업대
여러분이 창고에서 물건을 수리하는 기술자입니다.
- RAM: 작은 작업대. 3개만 올려놓을 수 있습니다.
- Disk: 거대한 창고. 물건이 수천 개 있습니다.
- Page Fault: "아, 이 물건 지금 작업대에 없네? 창고 가서 가져와야겠다."
작업대가 작으면? 물건을 자꾸 바꿔가며 작업해야 합니다. 이 과정이 느립니다. 창고까지 왕복하는 시간이 오래 걸리기 때문입니다.
이 비유들이 제 머릿속을 확 정리해줬습니다. 결국 가상 메모리는 환상이고, 스왑은 환상을 유지하기 위해 치르는 대가였습니다.
가상 메모리의 작동 원리 깊이 들여다보기
이제 좀 더 깊이 들어가 봅니다.
4.1 주소 변환 (Address Translation)
프로세스는 가상 주소(Virtual Address)를 봅니다. 예를 들어, 프로그램이 "메모리 주소 0x1000에 있는 값을 읽어줘"라고 요청합니다.
그런데 이 주소는 가짜입니다. 진짜 RAM의 주소가 아닙니다.
OS는 페이지 테이블(Page Table)을 사용해서 가상 주소를 물리 주소(Physical Address)로 변환합니다.
가상 주소 0x1000 → 페이지 테이블 조회 → 물리 주소 0x3000
이 과정이 MMU(Memory Management Unit)라는 하드웨어가 처리합니다.
4.2 페이지 테이블 (Page Table)
메모리는 페이지(Page)라는 단위로 나뉩니다. 보통 4KB입니다.
페이지 테이블은 이런 식으로 생겼습니다.
| 가상 페이지 번호 | 물리 페이지 번호 | 상태 |
|---|---|---|
| 0 | 3 | RAM에 있음 |
| 1 | - | Disk에 있음 (Swap) |
| 2 | 7 | RAM에 있음 |
| 3 | - | Disk에 있음 (Swap) |
프로세스가 가상 페이지 1번을 읽으려고 하면? 페이지 테이블을 보니 "Disk에 있음"이라고 써 있습니다. 이게 바로 페이지 폴트(Page Fault)입니다.
4.3 페이지 폴트 (Page Fault)
페이지 폴트가 발생하면 OS는 이렇게 행동합니다.
- 인터럽트 발생: CPU가 하던 일을 멈춥니다.
- Swap In: Disk에서 해당 페이지를 RAM으로 가져옵니다.
- 페이지 테이블 업데이트: "이제 RAM에 있어"라고 수정합니다.
- 프로세스 재개: 다시 실행합니다.
이 과정이 엄청 느립니다. Disk I/O는 RAM 접근보다 수천 배 느리기 때문입니다.
4.4 Demand Paging
OS는 필요할 때만(On Demand) 페이지를 RAM에 올립니다. 프로그램을 실행할 때 전체를 RAM에 올리는 게 아니라, 지금 당장 필요한 부분만 올립니다.
예를 들어, 크롬 브라우저를 실행하면 전체 프로그램이 100MB라고 칩시다. 그런데 실제로 지금 사용하는 건 10MB뿐입니다. OS는 10MB만 RAM에 올립니다. 나머지 90MB는 Disk에 그대로 둡니다.
사용자가 새로운 기능을 쓰려고 하면? 그때 가서 필요한 페이지를 RAM에 올립니다. 이게 Demand Paging입니다.
4.5 페이지 교체 알고리즘 (Page Replacement)
RAM이 꽉 찼는데 새로운 페이지를 올려야 한다면? 기존 페이지 중 하나를 쫓아내야 합니다. 이때 어떤 페이지를 쫓아낼지 결정하는 게 페이지 교체 알고리즘입니다.
대표적인 알고리즘들:
- FIFO (First In First Out): 가장 먼저 들어온 페이지를 쫓아냅니다. 단순하지만 비효율적입니다.
- LRU (Least Recently Used): 가장 오래 사용하지 않은 페이지를 쫓아냅니다. 현실적으로 가장 많이 씁니다.
- LFU (Least Frequently Used): 사용 빈도가 가장 낮은 페이지를 쫓아냅니다.
리눅스는 주로 LRU 변형을 사용합니다.
5. 스왑 공간 (Swap Space)
이제 스왑 공간에 대해 정리해봅니다.
5.1 Swap Partition vs Swap File
스왑 공간을 만드는 방법은 두 가지입니다.
Swap Partition (스왑 파티션)
디스크의 일부를 완전히 스왑 전용으로 할당합니다. 포맷도 스왑 전용 포맷으로 합니다.
장점:
- 성능이 약간 더 좋습니다. 파일 시스템 오버헤드가 없기 때문입니다.
- 단편화(Fragmentation)가 적습니다.
단점:
- 크기 변경이 어렵습니다. 디스크를 다시 파티셔닝해야 합니다.
Swap File (스왑 파일)
일반 파일 시스템 안에 큰 파일 하나를 만들어서 스왑으로 씁니다.
장점:
- 크기 변경이 쉽습니다. 그냥 파일 삭제하고 다시 만들면 됩니다.
- 설정이 간단합니다.
단점:
- 성능이 약간 떨어집니다. 파일 시스템 오버헤드가 있기 때문입니다.
요즘엔 Swap File을 더 많이 씁니다. 클라우드 환경에서는 특히 그렇습니다.
5.2 Swap 크기는 얼마로?
옛날 규칙: "RAM의 2배" 요즘 규칙: "상황에 따라 다름"
제가 받아들인 원칙은 이렇습니다.
- 개발 서버: RAM의 50% 정도. 가끔 메모리 부족할 때 버티는 용도.
- 프로덕션 서버: RAM의 20~30% 정도. OOM 방지용 보험. 스왑이 실제로 쓰인다면 알람 설정.
- 데이터베이스 서버: 스왑 거의 0. 스왑 쓰는 순간 성능이 심각하게 떨어지기 때문입니다.
6. 실제로의 적용
6.1 AWS EC2에서 Swap 설정하기
제가 했던 설정입니다.
# 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
6.2 Swappiness 설정
Swappiness는 "언제부터 스왑을 쓸까?"를 결정하는 값입니다. 0~100 사이입니다.
- 100: 스왑을 적극적으로 씁니다. RAM이 남아도 스왑을 씁니다.
- 0: 스왑을 거의 안 씁니다. RAM이 꽉 찰 때까지 버팁니다.
- 기본값 (Ubuntu): 60
제가 쓰는 값은 10입니다. "정말 급할 때만 스왑을 써라"는 의미입니다.
# 현재 값 확인
cat /proc/sys/vm/swappiness
# 임시 변경 (재부팅하면 사라짐)
sudo sysctl vm.swappiness=10
# 영구 변경
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
6.3 모니터링 - 스왑 사용량 체크
# 스왑 사용량 확인
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나 쓰고 있었습니다. 그 순간 "아, 이래서 느렸구나"라고 이해했습니다.
6.4 Docker 환경에서의 메모리 제한
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배를 스왑으로 자동 설정합니다.
6.5 OOM Killer
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
음수 값은 "이 프로세스는 중요하니까 죽이지 마"라는 의미입니다.
7. 스레싱 (Thrashing) - 시스템이 죽는 순간
스레싱은 제가 직접 경험한 악몽입니다.
7.1 스레싱이 뭔가요?
페이지 폴트가 너무 자주 일어나는 상태입니다. OS가 Swap In/Out하느라 정작 실제 작업은 못 합니다.
비유하자면, 도서관 사서가 책을 나르느라 정작 학생들은 책을 못 읽는 상황입니다.
7.2 언제 발생하나요?
RAM이 부족한데 프로세스가 많을 때 발생합니다.
예를 들어:
- RAM: 2GB
- 프로세스 10개, 각각 300MB씩 필요 → 총 3GB 필요
RAM이 모자라니 OS는 스왑을 씁니다. 그런데 프로세스들이 모두 동시에 활동하면? OS는 쉴 새 없이 Swap In/Out을 반복합니다.
7.3 증상
- CPU 사용률: 낮음 (10~20%)
- Disk I/O: 극도로 높음 (100%)
- 시스템 응답: 거북이
- 하드디스크 LED: 계속 깜빡임
제 서버가 이 상태였을 때, SSH 접속도 1분씩 걸렸습니다.
7.4 해결 방법
근본 해결책: RAM을 늘리거나, 프로세스를 줄입니다.
임시 해결책:
- 불필요한 프로세스 종료
- Swappiness 값 낮추기
- 일부 서비스 다른 서버로 이동
8. 정리 - 거짓말에는 대가가 따른다
| 구분 | 기본 상태 | 가상 메모리 & 스왑 |
|---|---|---|
| 비유 | 책상 크기만큼만 책을 폄 | 책장을 책상인 척 씀 |
| 장점 | 엄청 빠름 (All RAM) | 용량 한계 극복 가능 |
| 단점 | 비쌈, 용량 부족 시 뻗음 | 느림 (디스크 속도) |
| 결과 | 프로그램 강제 종료 (OOM) | 버벅임 (Thrashing) |
저는 이걸 이렇게 이해했습니다.
가상 메모리는 OS의 환상입니다. "우리 집 메모리 무한대야"라고 속입니다. 스왑은 환상을 유지하기 위해 디스크를 몰래 씁니다. 대가는 속도입니다.
"스왑 메모리 좀 늘려서 해결하죠?"
이 말은 "일단 급한 불만 끄고, 속도 느려지는 건 나중에 생각하시죠?"라는 말과 같습니다.
진짜 해결책은 RAM을 늘리는 것입니다. 스왑은 보험입니다. 보험은 쓰지 않는 게 최선입니다.