운영체제 3편 — 메모리 관리, 없는 것을 있는 척하는 기술
Published: June 22, 2026
2018년, AWS는 서버리스 컴퓨팅의 핵심 기술인 Firecracker를 공개했다. 하나의 물리 서버 위에서 수천 개의 프로그램을 동시에 실행하기 위해, AWS는 서버가 실제로 가진 메모리보다 더 많은 메모리를 나눠주는 방식을 택했다. 어차피 수천 개의 프로그램이 동시에 전부 동작하지는 않을 것이라는 판단이었다. 그리고 대부분의 시간 동안 이 판단은 맞았다.
같은 원리가 2008년 월스트리트에서는 정반대의 결과를 낳았다. 당시 리먼 브라더스는 자신들이 실제로 보유한 자금보다 31배나 많은 자산을 굴리고 있었다. 모든 사람이 동시에 돈을 돌려달라고 요구하지는 않을 것이라는 전제 덕분이었다. 그러나 이 전제가 무너지면서 리먼 브라더스는 미국 역사상 최대 규모의 파산을 신청했다.
Reference: AWS: Firecracker – Lightweight Virtualization for Serverless Computing (2018)
Reference: Firecracker Internals: “over-commit resources” (2021)
Reference: Economics Observatory: Why did Lehman Brothers fail? (2023)
하나는 성공했고 하나는 무너졌지만, 두 케이스가 취한 전략은 같다. 실제 가진 것보다 더 많은 것을 약속하고, 모두가 동시에 그 약속을 쓰러 오지 않기만을 바라는 것이다.
호텔이나 항공사가 객실과 좌석을 초과 예약(오버부킹)하는 원리가 정확히 그렇다. 예약 취소나 노쇼(No-Show)가 발생한다는 통계적 확신이 있기에, 실제 수량보다 더 많은 손님을 받는다.
운영체제도 마찬가지다. 컴퓨터의 메모리는 한정되어 있지만, 운영체제는 프로그램들에게 훨씬 넉넉한 공간이 있는 것처럼 행동한다. 대부분의 시간 동안 이 약속은 문제없이 지켜진다. 문제는 그 자원을 한꺼번에 쓰겠다고 몰려들 때 발생한다.
1편의 조율 비용, 2편의 탐색 비용에 이어 이번 편의 주제는 착시 비용이다. 바로 없는 것을 있는 척하기 위해 운영체제가 치르는 대가에 대한 이야기다.
RAM은 왜 항상 부족한가
섹션 제목: “RAM은 왜 항상 부족한가”오버부킹은 단순히 위험을 감수하는 선택이 아니다. 호텔과 항공사는 통계적으로 일부 예약자가 나타나지 않는다는 사실을 이용해, 실제 보유량보다 많은 예약을 받는다. 중요한 것은 가진 자원의 크기가 아니라, 실제 사용량이 약속한 최대치보다 낮게 유지될 것이라는 가정이다.
운영체제도 정확히 같은 선택을 한다. 비싸고 한정적인 RAM의 특성상 프로세스들은 언제나 실제로 사용하는 것보다 더 많은 메모리를 요청하지만, 그 요청한 메모리 전체를 동시에 사용하는 경우는 극히 드물다. 그래서 운영체제는 실제 RAM보다 더 많은 메모리를 약속한다.
호텔이 오버부킹을 하듯, 운영체제도 메모리를 오버커밋(overcommit)하는 것이다. 이 구조에서 핵심은 메모리를 물리적으로 늘리는 것이 아니다. 부족함을 끝까지 들키지 않는 것이다.
가상 메모리 - 운영체제가 하는 가장 거대한 거짓말
섹션 제목: “가상 메모리 - 운영체제가 하는 가장 거대한 거짓말”오버부킹을 감행한 호텔이 실패하지 않으려면 한 가지 조건이 필요하다. 문을 열고 들어오는 고객이 “내 방은 어디에 있느냐”고 물었을 때, 방이 부족하다는 사실을 끝까지 들키지 않아야 한다.
운영체제도 같은 문제를 겪는다. 프로그램마다 자신만의 메모리 공간이 필요하지만, 모든 프로그램의 요구량을 실제 RAM만으로 감당하는 것은 불가능하다. 이 자원 한계를 해결하는 기술이 가상 메모리(Virtual Memory)다.
일단 요청을 전부 수락하고, 프로그램에게 거대한 공간이 있는 것과 같은 착시를 제공하는 것이다. 실제 물리 공간은 미리 내어주지 않는다. 대신 프로그램이 데이터를 읽거나 쓰는 순간에만 실제 RAM의 빈자리를 실시간으로 연결한다.
[ Virtual Memory ]
프로세스 A "내 4GB"프로세스 B "내 4GB"프로세스 C "내 4GB" ↓ ┌───────────┐ │ 실제 RAM │ │ 8GB │ └───────────┘
→ 모두가 4GB를 가졌다고 믿지만, 실제로는 같은 RAM을 공유하고 있다.이 구조 아래에서 모든 프로그램은 자신이 전체 메모리를 혼자 쓰고 있다고 믿고 동작한다. 다른 프로그램의 존재나 실제 물리적 RAM의 총량이 얼마인지 신경 쓸 필요가 없다.
뒤에서는 운영체제가 실제 RAM의 빈 공간을 실시간으로 쪼개고 재배치하지만, 프로그램은 이 과정을 인지하지 못한다. 실제 필요해지는 순간에만 자원을 연결하여 이러한 착시를 유지한다.
착시는 어떻게 유지되는가
섹션 제목: “착시는 어떻게 유지되는가”문제는 여기서부터다. 실제 물리적 RAM보다 훨씬 많은 용량을 프로그램들에게 약속했다면, 이 상태를 어떻게 문제없이 유지할 수 있을까?
원리는 단순하다. 약속은 지금 하고, 이행은 나중으로 미루는 것이다.
호텔이 예약을 받을 때 객실을 즉시 제공하지 않듯, 운영체제도 메모리 요청이 들어왔다고 해서 곧바로 RAM을 할당하지 않는다. 대신 할당했다는 기록만 먼저 남겨둔다. 그리고 정말로 그 자원이 필요해지는 순간에 비로소 실제 자원을 내어준다.
운영체제는 이 착시를 유지하기 위해 두 가지 장치를 사용한다.
- Lazy Allocation(지연 할당): 자원 제공 자체를 실제 사용 시점까지 최대한 지연.
- Page Fault(페이지 폴트): 미뤄둔 약속이 실제 이행되어야 하는 시점.
가상 메모리가 만드는 착시는 이 두 장치가 함께 작동할 때 비로소 유지된다.
Lazy Allocation - 이행을 미루는 기술
섹션 제목: “Lazy Allocation - 이행을 미루는 기술”호텔 예약이 확정되었다고 해서 객실을 미리 비워두지는 않는다. 고객이 실제 프론트 데스크에 도착해 체크인하기 전까지 방은 비어 있거나 다른 손님에게 배정된다.
운영체제가 메모리를 다루는 방식도 이와 같다. 만약 프로그램이 100MB를 요청해 두고 실제로는 10MB 영역만 사용한다면, 운영체제는 10MB만큼의 비용만 실제 RAM에서 제공한다. 나머지 90MB는 약속으로만 존재할 뿐, 실제 자원은 아직 인도되지 않은 상태다.
[ Lazy Allocation ]
메모리 요청 실제 접근 물리 메모리 할당┌──────────┐ ┌──────────┐ ┌──────────┐│ Program │ │ Program │ │ RAM ││ 100MB │ ───────▶ │ Page A │ ───────▶ │ Page A ││ 요청 │ │ 접근 │ │ 할당 │└──────────┘ └──────────┘ └──────────┘ │ │ ▼ ▼ 약속만 존재 Page Fault (비용 지연) (비용 청구)이것이 Lazy Allocation(지연 할당)이다. 메모리를 요청한 시점이 아니라, 실제로 사용하는 시점에 물리 메모리를 할당하는 방식이다.
Page Fault - 미뤄둔 비용의 청구
섹션 제목: “Page Fault - 미뤄둔 비용의 청구”예약은 이미 받아뒀지만 실제 객실은 아직 배정되지 않았다. 고객이 호텔 로비에 도착해 체크인을 요청하는 순간, 호텔은 그제야 비어 있는 방을 찾아 열쇠를 건넨다.
운영체제도 같은 방식으로 움직인다. 프로그램이 메모리를 요청했다고 해서 실제 RAM을 즉시 내어주지는 않는다. 그러다 프로그램이 해당 주소에 접근하는 순간, CPU는 아직 연결된 물리 메모리가 없다는 사실을 발견하고 운영체제에게 도움을 요청한다. 이것이 Page Fault다.
결국 Page Fault는 시스템의 오류가 아니다. 착시로 만들어둔 가상 공간을 실제 물리 자원으로 채워야 하는 순간을 의미한다. 이 비용 청구가 감당 가능한 빈도로 발생하는 한, 가상 메모리 체계는 문제없이 작동한다.
복사하지 않고 복사하기
섹션 제목: “복사하지 않고 복사하기”운영체제의 착시 전략은 “없는 메모리를 있는 척”하는 것에서 끝나지 않는다. 한 단계 더 나아간 착시가 있다. 복사하지 않았는데 복사한 것처럼 보이게 만드는 것이다.
현재 실행 중인 프로세스를 그대로 복제해야 할 때, 가장 정직한 방법은 메모리 전체를 똑같이 하나 더 만드는 것이다. 하지만 복사하는 행위는 막대한 시간과 자원을 소모한다. 만약 복사해 두고 정작 데이터를 거의 수정하지 않거나 곧바로 다른 프로그램으로 바꾸어 실행한다면, 이 정직한 작업은 거대한 낭비가 된다.
운영체제는 이 비효율을 피하기 위해 또 한 번 비용을 뒤로 미루는 속임수를 선택한다.
Copy-on-Write - 수정 전까지의 공유
섹션 제목: “Copy-on-Write - 수정 전까지의 공유”운영체제는 복사 명령이 떨어져도 실제로 복사하지 않는다. 대신 부모와 자식 프로세스가 같은 물리 메모리를 함께 가리키게 만든다. 겉으로는 완벽히 복사된 것처럼 보이지만, 수면 아래에서는 하나의 자원을 공유하며 비용 지출을 늦추는 것이다.
[ Copy-on-Write - fork() 직후 ]
부모 프로세스 자식 프로세스 │ │ ▼ ▼┌─────────────┐ ┌─────────────┐│ 가상 페이지 A │ │ 가상 페이지 A │└──────┬──────┘ └──────┬──────┘ └─────────┬─────────┘ ▼ ┌─────────────┐ │ 물리 페이지 A │ │ DATA │ └─────────────┘
하나의 물리 페이지를 두 프로세스가 공유운영체제가 미뤄둔 비용은 데이터 변경이 발생하는 시점에 드러난다. 둘 중 하나의 프로세스가 데이터를 수정하는 순간에만, 해당 페이지만 실제로 복사하여 분리한다. 이것이 Copy-on-Write(CoW)다.
[ Copy-on-Write - 자식 프로세스가 Page A 수정 ]
부모 프로세스 자식 프로세스 │ │ ▼ ▼┌─────────────┐ ┌──────────────┐│ 가상 페이지 A │ │ 가상 페이지 A │└──────┬──────┘ └──────┬───────┘ ▼ ▼┌─────────────┐ ┌──────────────┐│ 물리 페이지 A │ │ 물리 페이지 A' ││ DATA │ │ NEW DATA │└─────────────┘ └──────────────┘
원본 유지 새로운 페이지 할당Redis가 RDB 스냅샷을 생성할 때도 이 원리가 정확히 적용된다. 데이터 전체를 즉시 복사하는 대신, fork() 이후 자식 프로세스와 메모리 공유 상태를 유지한다. 메인 프로세스가 데이터를 수정할 때, 변경된 페이지만 실시간으로 복사하기 때문이다. 덕분에 수 GB의 스냅샷을 찍어도 순간적인 자원 고갈이 발생하지 않는다.
이 전략 덕분에 시스템은 대규모 복사 부담에서 벗어난다. 자원 소모를 ‘지금 당장 무조건’ 해야 하는 일에서, 데이터가 바뀔 때만 치르는 선택으로 바꾼 셈이다.
모두가 동시에 진실을 요구하는 순간
섹션 제목: “모두가 동시에 진실을 요구하는 순간”앞서 본 항공사의 오버부킹처럼 이유는 단순하다. 모든 승객이 동시에 탑승하러 오는 일은 드물기 때문이다. 빈 좌석이라는 낭비를 줄이기 위한 선택이며, 대부분의 비행에서 이 전략은 성공한다. 문제는 예약한 승객이 단 한 명도 빠짐없이 공항에 나타나는 날이다.
가상 메모리도 정확히 같은 전제 위에서 작동한다. 운영체제는 모든 프로그램이 자신에게 할당된 메모리를 동시에 전부 사용하지 않을 것이라 가정하고, 실제 RAM보다 훨씬 많은 공간을 약속(오버커밋)한다. 그리고 약속된 몫을 일시에 요구하기 시작하면, 가상 메모리가 버텨온 착시도 결국 한계를 맞이한다.
Paging과 Swapping - 착시를 유지하기 위한 대가
섹션 제목: “Paging과 Swapping - 착시를 유지하기 위한 대가”초과 예약된 승객이 전부 나타나면, 항공사는 누군가를 다른 비행기로 보내고 보상금을 지급해야 한다. 효율을 위해 감수한 선택이 물리적 한계에 부딪혀 예기치 못한 추가 비용으로 돌아오는 것이다.
모든 프로그램이 동시에 메모리를 사용해 물리적 RAM 공간이 부족해지면, 운영체제도 똑같은 선택을 한다. 당장 쓰이지 않는 메모리 영역을 디스크의 Swap(스왑)영역으로 밀어내고, 그 빈자리에 지금 필요한 데이터를 올리는 Paging(페이징)을 수행한다.
이 과정에서 착시를 유지하기 위한 대가가 청구된다. 디스크의 처리 속도는 RAM에 비해 압도적으로 느리기 때문이다.
[ 메모리 계층 속도 비교 ]
CPU Cache ██ (~1ns)RAM ████████ (~100ns)SSD ████████████████████████████ (~100μs)HDD ██████████████████████████████████████████ (~10ms)
→ RAM에서 디스크로 밀려나는 순간, 접근 속도는 수천 배 느려진다.프로그램은 여전히 빠른 메모리에 접근하고 있다고 믿지만, 뒤에서 운영체제는 수천 배 느린 디스크를 처리하며 착시를 유지하느라 시간을 소모한다. 시스템의 전반적인 속도가 떨어지기 시작하는 이유다.
Thrashing - 착시 유지 비용이 생산 비용을 역전하는 순간
섹션 제목: “Thrashing - 착시 유지 비용이 생산 비용을 역전하는 순간”상황이 임계점을 넘으면 항공사는 항의를 처리하고, 보상안을 조율한다. 정작 비행기를 한 대도 띄우지 못하는 마비 상태에 빠진다. 효율을 높이려던 전략이 도리어 운영 자체를 방해하는 시점이다.
운영체제에서 이 지점을 Thrashing(쓰레싱)이라 부른다. RAM과 디스크 사이에서 데이터를 복사하고 교체하는 오버헤드가 너무 커져서, 정작 프로그램의 실제 연산은 거의 처리하지 못하는 상태를 말한다.
Thrashing은 단순히 메모리가 부족한 상태가 아니다. CPU와 디스크는 쉬지 않고 움직이며 무언가를 열심히 하지만, 실제 가치 있는 작업은 아무것도 생산하지 못한다. 착시를 유지하는 비용이 실제 작업 비용을 완전히 역전한 것이다.
사라지지 않는 제약
섹션 제목: “사라지지 않는 제약”여기서 중요한 것은 운영체제가 RAM 부족을 해결했다는 사실이 아니다. RAM이라는 물리적 제약은 시스템 깊은 곳에 여전히 그대로 존재한다. 운영체제가 한 일은 제약을 제거한 것이 아니라, 제약이 시스템을 멈추게 만드는 순간을 뒤로 늦춘 것이다.
이것이 엘리야후 골드랫의 제약이론(Theory of Constraints)이 말하는 핵심이다. 모든 시스템의 전체 처리량은 가장 부족한 자원인 ‘제약(Constraint)‘에 의해 결정된다. 공장의 생산 속도가 가장 느린 공정에 묶이듯, 운영체제의 처리량은 언제나 한정된 RAM에 의해 결정된다.
가상 메모리는 이 제약 자체를 없앤 것이 아니라, 제약이 드러나는 시점을 교묘하게 지연했을 뿐이다.
- Lazy Allocation: 실제 자원 지급을 미룸으로써 제약의 노출을 1차로 미룬다.
- Page Fault: 유예했던 제약이 처음으로 드러나는 순간이다. 운영체제가 개입해 실시간으로 약속을 현실화한다.
- Swapping: 제약이 제어 한계를 넘어서기 시작한다. 부족함을 감추기 위해 느린 디스크까지 동원해야 한다.
- Thrashing: 제약이 시스템 전체를 지배한다. 실제 작업보다 제약을 숨기고 관리하는 데 모든 자원을 소모한다.
1편의 조율 비용, 2편의 탐색 비용에 이어 가상 메모리가 다룬 본질은 착시의 비용이다. 비용은 완전히 제거할 수 없으며, 형태만 바꿀 수 있을 뿐이다. 오버커밋이 만드는 착시는 제약이 드러나지 않는 안전한 구간 내에서는 최고의 효율을 보장한다. 하지만 임계점을 넘어 실제 자원 요구가 한데 몰리면, 그동안 미뤄둔 대가를 한 번에 치러야 한다.
제약은 사라지지 않는다. 다만 드러나는 시점을 늦출 수 있을 뿐이다.
정리하며
섹션 제목: “정리하며”운영체제는 더 많은 메모리를 만들지 않는다. 대신 부족한 메모리가 충분한 것처럼 보이게 만든다.
대부분의 시간 동안 이 전략은 놀랍도록 잘 동작한다. 프로그램은 자신에게 넉넉한 메모리가 주어진 것처럼 행동한다. 하지만 운영체제가 해결한 것은 RAM 부족이라는 문제 자체가 아니다.
RAM 부족이라는 위기를 가능한 한 늦게 마주하게 만든 것에 가깝다.
결국 제약은 사라지지 않는다. 모두가 동시에 약속을 사용하려는 순간, 운영체제가 유지하던 착시는 깨지기 시작한다.
가상 메모리는 메모리 기술이 아니다. 제약을 숨기는 기술이다.