물리 메모리가 가득 찼을 때 어떤 페이지를 내보낼지 고르는 것.
알고리즘
- OPT — 앞으로 가장 늦게 쓸 페이지를 내보낸다 — 이론적 최적. 미래를 알아야 해서 불가능
- (다른 알고리즘을 평가하는 기준으로만 쓴다)
- FIFO — 먼저 들어온 것부터 — 단순하지만 자주 쓰는 페이지도 내보낸다
- Belady 이상: 프레임을 늘렸는데 폴트가 더 늘어나는 역설이 생긴다
- LRU — 가장 오래 안 쓴 것부터 — 지역성에 잘 맞는다
- 정확히 구현하려면 접근마다 순서를 갱신해야 해 비용이 크다
- Clock — LRU 근사. 참조 비트를 시계처럼 돌며 확인한다 — 실제 OS 가 쓰는 방식
Clock이 실용적인 이유
각 페이지에 참조 비트(R)가 있다. 접근하면 하드웨어가 1로 만든다
시곗바늘이 돌면서
-
R = 1 이면 → 0 으로 바꾸고 지나간다 (한 번 봐준다)
-
R = 0 이면 → 이걸 내보낸다
-
접근할 때마다 리스트를 갱신할 필요가 없다 (하드웨어가 비트만 세운다)
스래싱
동시에 도는 프로세스들의 워킹셋 합이 물리 메모리를 넘으면
-
페이지를 내보낸다 → 곧 그 페이지가 다시 필요하다 → 다시 가져온다
-
또 다른 걸 내보낸다 → 그것도 곧 필요하다 → ...
-
CPU 는 놀고 디스크만 미친 듯이 돈다
-
처리량이 급락한다
증상: CPU 사용률은 낮은데 시스템이 극도로 느리다. 디스크 I/O 가 폭주한다
vmstat 1
# r b swpd free si so ... us sy id wa
# 1 5 2.1G 50M 2400 1800 5 8 2 85
# ^^^^ ^^^^ 스왑 in/out 이 크면 스래싱
# ^^ I/O 대기가 지배적
처방은 하나뿐이다 — 동시 실행 수를 줄이거나 메모리를 늘린다. 알고리즘을 바꿔서 해결되지 않는다.
최후의 수단 — OOM Killer
스왑도 다 차면 커널이 프로세스를 골라 죽인다
점수(oom_score)가 높은 것부터: 메모리를 많이 쓰고 · 오래 안 됐고 · 중요도가 낮은 것
dmesg | grep -i "killed process"
cat /proc/<pid>/oom_score
echo -1000 > /proc/<pid>/oom_score_adj # 이 프로세스는 최후까지 살린다
컨테이너에서는 cgroup 한도를 넘으면 그 컨테이너만 OOMKilled 된다 — 호스트 전체 메모리와 무관하게 발생하므로 헷갈리기 쉽다.
단편화 두 종류
- 내부 단편화 — 할당 단위보다 적게 써서 남는 공간 (4KB 페이지에 100바이트만 쓰면 3.9KB 낭비)
- 외부 단편화 — 빈 공간의 총합은 충분한데 연속된 큰 덩어리가 없다
- 페이징은 크기가 같아 이 문제가 없다
면접 함정
- ❌ "LRU가 최적이다" → OPT가 최적이고 LRU는 근사다. 실제 OS는 그보다 더 근사한 Clock을 쓴다.
- ❌ "스왑을 끄면 빨라진다" → 스래싱은 사라지지만 OOM으로 죽는다. 원인은 메모리 부족이다.
워킹셋
프로세스가 '최근 일정 시간 동안 실제로 접근한 페이지 집합'
- 워킹셋이 물리 메모리에 들어가면 — → 폴트가 드물다. 잘 돈다
- 워킹셋 합이 물리 메모리를 넘으면 — → 스래싱
그래서 용량 산정은 '전체 메모리 사용량' 이 아니라 '워킹셋' 기준이어야 한다
스왑을 얼마나 쓸지
sysctl vm.swappiness
# 0~100. 높을수록 적극적으로 스왑한다 (기본 60)
# DB 서버는 보통 1~10 으로 낮춘다 — 지연 스파이크를 피하려고
# 0 이어도 메모리가 정말 부족하면 스왑한다 (완전 금지가 아니다)
캐시와 경쟁한다
페이지 캐시(파일 데이터)와 익명 페이지(힙·스택)가 같은 물리 메모리를 나눠 쓴다
큰 파일을 읽으면 페이지 캐시가 부풀어 애플리케이션 페이지를 밀어낼 수 있다
- 백업·로그 처리 후 서비스가 느려지는 현상
- posix_fadvise(DONTNEED) 로 "이건 캐시 안 해도 된다" 를 알려 줄 수 있다