デマンドページングとスラッシング — メモリが足りないとき起きること
한국어 원문으로 표시합니다.
한 줄 요약
페이지를 필요할 때만 올리면 물리 메모리보다 큰 프로그램도 돌릴 수 있지만, 폴트 한 번의 비용이 메모리 접근보다 수만 배 비싸므로 폴트율이 조금만 올라도 시스템이 무너진다.
왜 이게 필요했나
프로그램 전체를 메모리에 올릴 필요는 없다. 오류 처리 코드처럼 거의 실행되지 않는 부분도 많고, 큰 배열의 일부만 실제로 쓰이는 경우도 흔하다. 필요한 시점에만 올리면(요구 페이징) 더 많은 프로세스를 동시에 돌릴 수 있고, 프로그램 시작도 빨라진다.
어떻게 동작하나
페이지 테이블 항목에 유효 비트를 둔다. 유효하지 않은 페이지에 접근하면 페이지 폴트가 발생하고 다음 순서로 처리된다.
- 트랩이 걸려 커널로 진입한다.
- 잘못된 접근인지(주소 범위 밖) 아니면 아직 안 올린 것인지 판단한다.
- 빈 프레임을 찾는다. 없으면 희생 페이지를 골라 내보낸다. 그 페이지가 수정된 상태(dirty)면 먼저 디스크에 기록한다.
- 디스크에서 페이지를 읽어 프레임에 올린다.
- 페이지 테이블을 갱신하고, 폴트를 일으킨 명령어부터 다시 실행한다.
비용을 계산해 보면 무섭다. 메모리 접근이 200ns 이고 페이지 폴트 처리가 8ms 라고 하자. 폴트 확률이 p 일 때 실효 접근 시간은 (1 빼기 p) 곱하기 200 더하기 p 곱하기 8,000,000 이다.
- p = 0.001, 즉 천 번에 한 번 폴트가 나면 실효 접근 시간은 약 8,200ns 로, 폴트가 없을 때보다 40배 느리다.
- 성능 저하를 10퍼센트 이내로 묶으려면 p 가 약 0.0000025 이하여야 한다. 40만 번에 한 번이다.
페이지 교체 알고리즘의 성격도 알아 둘 만하다. 참조열 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1 에 프레임 3개를 주면 FIFO 는 폴트 15회, LRU 는 12회, 이론적 최적인 OPT 는 9회다. FIFO 에는 프레임을 늘렸는데 폴트가 늘어나는 벨래디의 이상현상까지 있다. 실무 시스템은 순수 LRU 대신 참조 비트를 이용한 클럭(second-chance) 알고리즘을 쓴다. LRU 에 가까운 성능을 훨씬 싼 비용으로 얻기 때문이다.
스래싱은 프레임이 부족해 폴트가 폭증하고, 그 때문에 CPU 가 놀고, 운영체제가 "한가하니 프로세스를 더 올리자"고 판단해 상황이 더 나빠지는 악순환이다. 대응은 워킹 셋(최근 일정 구간에서 실제로 참조된 페이지 집합)을 추적해 그만큼의 프레임을 보장하거나, 폴트율을 직접 모니터링해 프레임을 조절하는 것이다.
현장에서 만나는 모습
컨테이너에 메모리 제한을 걸었을 때 두 가지 결말이 있다. 스왑이 꺼져 있으면 OOM Killer 가 프로세스를 죽인다. 갑작스럽지만 원인은 명확하다. 스왑이 켜져 있으면 대신 스래싱이 시작된다. 프로세스는 살아 있는데 응답이 수십 배 느려지고, CPU 사용률은 낮은데 iowait 만 치솟는다. 죽는 편이 차라리 진단하기 쉽다는 말이 나오는 이유다. 쿠버네티스가 오랫동안 스왑을 끄도록 요구했던 배경에도 이 예측 불가능성이 있다.
이어지는 퀴즈에서 확인할 것
페이지 폴트 비용 계산을 직접 할 수 있는지, 스래싱이 왜 스스로를 강화하는 악순환인지 설명할 수 있는지 확인한다.