운영체제가 가상 주소를 물리 주소로 바꾸는 데 쓰는 페이지 테이블은 보통 메모리 관리의 배경에 머물러 있는 구조물이다. 매핑된 메모리의 약 1/512, 즉 1% 미만만 차지하기 때문에 대부분의 상황에서 신경 쓸 필요가 없다. 그러나 이 '무시해도 되는 비용'이라는 전제는 하나의 페이지가 한 번만 매핑될 때만 성립한다. 같은 메모리를 여러 주소 공간이 동시에 매핑하는 순간, 페이지 테이블은 조용히 데이터 자체만큼의 메모리를 집어삼키기 시작한다.
트리 구조는 왜 선택되었나
리눅스 커널이 다단계(multi-level) 트리형 페이지 테이블을 채택한 배경에는 명확한 성능 논리가 있다. 2003년 리누스 토르발스는 해시 페이지 테이블을 강하게 비판하면서, 트리 구조에서는 인접한 페이지의 엔트리가 메모리상에서도 인접하기 때문에 캐시 라인 하나로 여러 TLB 엔트리를 한꺼번에 채울 수 있다고 주장했다. 그의 설명에 따르면 평범한 인텔 CPU는 한 번에 8개의 TLB 엔트리를 가져오며, 자기 매핑(self-mapping) 기법과 결합하면 단 한 번의 메모리 참조로 Power 계열의 해시 테이블보다 8배 넓은 커버리지를 얻는다. 해시 테이블은 이웃한 페이지들을 서로 다른 버킷에 흩어놓기 때문에 이 이점을 누릴 수 없다.
흥미로운 점은 설계 당시의 우선순위다. 토르발스는 1997년 석사 논문에서 다단계 페이지 테이블 채택을 설명하며 지연 시간(latency)에 큰 관심을 보였지만, 메모리 크기는 '부차적 관심사'로 분류했다. 매핑 정보가 파일 시스템 캐시나 사용자 프로그램에 쓰일 물리 메모리를 과도하게 차지하지 않아야 한다는 정도의 고려였다. 이 '부차적'이라는 판단이 20여 년에 걸쳐 반복적으로 도전받게 된다.
공유 매핑이 깨는 1/512 공식
핵심은 여러 주소 공간이 같은 페이지를 매핑할 때 각자 자신의 PTE(페이지 테이블 엔트리)를 따로 가져야 한다는 점이다. 4KiB 페이지 하나를 512개의 프로세스가 매핑하면 8바이트짜리 PTE가 512개 필요하고, 그 합은 정확히 4KiB — 데이터 페이지 자체와 같아진다. 결국 페이지 테이블 비용은 N/512로 늘어난다.
이 패턴은 메일링 리스트의 희귀한 사례처럼 보이지만 실제로는 꾸준히 재발한다. 2002년 안드레아 아르칸젤리는 64GiB x86 장비에서 여유 메모리가 남았는데도 OOM이 나는 현상을 추적했다. 수백 개 프로세스가 동일한 1GiB 공유 메모리를 매핑하면서, 데이터는 공유됐지만 페이지 테이블은 프로세스마다 PAE 기준 약 2MiB씩 32비트 x86의 좁은 lowmem(약 896MiB) 영역에 쌓인 것이 원인이었다. 그는 페이지 테이블을 highmem으로 옮기는 패치로 해결했다. 2022년 칼리드 아지즈는 512GB 램의 오라클 DB에서 1,500개 이상의 클라이언트가 300GB SGA에 붙자 OOM으로 죽는, 거의 동일한 구조의 문제를 보고했다. 최악의 경우 PTE만으로 878GB가 필요하다는 계산이었고, 그는 프로세스 간 페이지 테이블 공유를 위한 mshare를 제안했지만 아직 병합되지 않았다.
공유가 전혀 없어도 문제가 생기는 변종도 있다. 2021년 치 정은 단일 프로세스가 590GiB RSS에 110GiB의 페이지 테이블을 쓰는 사례를 보고했다. 정상이라면 약 1.2GiB면 충분한 양이다. 원인은 jemalloc과 tcmalloc이 메모리를 커널에 돌려줄 때 munmap() 대신 madvise(MADV_DONTNEED)를 쓰는 데 있었다. 이 호출은 데이터 페이지를 해제하고 PTE를 비우지만 페이지 테이블 자체는 그대로 남겨, 빈 페이지 테이블이 계속 쌓였다. 여러 차례 재작성을 거친 이 패치는 2025년에야 병합됐다.
데이터베이스와 휴지 페이지
이 현상은 대용량 공유 버퍼를 쓰는 데이터베이스에서 특히 선명하게 드러난다. 2021년 퍼코나의 요빈 어거스틴은 192GB 장비에서 공유 버퍼 138GiB, 연결 80개뿐인 포스트그레스가 OOM으로 죽는 상황을 재현했다. 백엔드들이 캐시를 건드릴수록 페이지 테이블이 45MiB에서 25GiB 이상으로 불어나 스와핑이 시작됐다. 2026년 클릭하우스의 카우식 이스카는 성장 폭을 직접 측정했다. 128GiB 장비에서 15.6GiB 캐시 테이블을 스캔하는 백엔드 하나가 31MiB씩 페이지 테이블을 더했고, 연결 200개에서는 6.1GiB에 달했다. 두 사례 모두 휴지 페이지(huge pages)로 해결됐다 — 같은 워크로드에서 페이지 테이블이 각각 61MiB, 111MiB로 억제됐다.
다만 휴지 페이지는 공짜가 아니다. 예약하려면 조각나지 않은 연속 메모리가 필요한데, 부하가 걸린 장비에서는 '같은 요청이 일상적으로 실패'하며 투명 휴지 페이지(THP)는 할당 과정에서 멈춰설 수 있다. 2016년 멜 고먼은 THP를 위한 커널의 기본 조각 모음을 중단하자고 제안하며 '수년이 지났으니 포기할 때'라고 말했고, 넬슨 엘헤이지는 THP가 유발할 수 있는 메모리 누수와 CPU 사용량 문제를 지적한 바 있다. 실무자 입장에서는 휴지 페이지가 만능 해법이 아니라 또 다른 운영 리스크를 안고 들어온다는 뜻이다.
NUMA가 더한 새로운 난제
NUMA 장비는 여기에 지연 시간이라는 축을 추가한다. 여러 노드 환경에서 다른 노드에 올라간 스레드는 TLB 미스가 날 때마다 원격 메모리에 놓인 페이지 테이블을 걸어야 한다. Mitosis(ASPLOS 2020)는 원격 페이지 테이블이 원격 데이터만큼 애플리케이션을 느리게 만들 수 있음을 보였고, Hydra(USENIX ATC 2024)는 8TB 램의 8소켓 장비에서 이를 재현했다. Mitosis는 페이지 테이블 트리 전체를 모든 노드에 복제해 '크기×노드 수'의 메모리를 쓰고 변경 때마다 모든 사본을 갱신해야 한다. Hydra는 특정 노드의 스레드가 폴트를 낼 때만 해당 PTE를 그 노드로 복사하고, 각 페이지 테이블 페이지가 사본을 가진 노드 목록을 관리하는 방식으로 비용을 줄였다.
결국 NUMA에서는 한 벌만 유지하며 TLB 미스 때 원격 접근 비용을 치르거나, 노드마다 사본을 두고 동기화 비용을 치르거나 둘 중 하나다. 설계 당시 '부차적'으로 분류됐던 페이지 테이블의 메모리 비용이, 수백 개 연결이 수십~수백 GiB의 공유 메모리를 함께 매핑하는 오늘날의 데이터베이스와 대용량 서버에서는 결코 부차적이지 않다는 사실을 이 사례들은 반복해서 보여준다. 공유 메모리 규모가 큰 워크로드를 운영한다면, memory.stat의 페이지 테이블 지표를 모니터링 대상에 포함하고 휴지 페이지 도입의 득실을 사전에 저울질해 둘 필요가 있다.