NUMA(Non-Uniform Memory Access)는 여러 CPU 소켓이 각자 가까운 메모리를 갖고, 다른 소켓의 메모리에 접근할 때는 더 느린 구조를 말한다. hugepages는 기본 4KB인 메모리 페이지 대신 2MB·1GB 같은 큰 페이지를 써서 주소 변환 비용을 줄이는 기법이다. 둘 다 "메모리를 어디에, 어떤 크기로 두느냐"로 성능을 끌어올리는 메모리 지역성(locality) 튜닝의 핵심이다.
솔직히 작은 서버만 만질 땐 평생 몰라도 됐다. 그런데 코어 수십 개짜리 큰 장비에서 DB나 인메모리 캐시를 돌리다 보면, 코드는 그대로인데 스레드 배치만 바꿔도 처리량이 출렁이는 걸 만난다. 그 정체가 대부분 NUMA다.
NUMA: 가까운 메모리와 먼 메모리
옛날 단일 소켓 시절엔 모든 CPU 코어가 하나의 메모리에 똑같은 거리로 접근했다(UMA). 지금의 2소켓 이상 서버는 다르다. 각 소켓(NUMA 노드)에 로컬 메모리가 붙어 있고, 다른 노드의 메모리를 읽으려면 소켓 간 연결(인터커넥트)을 건너야 한다. 이 원격 접근은 로컬보다 눈에 띄게 느리다.
문제는 리눅스 스케줄러가 스레드를 이 코어 저 코어로 옮길 수 있다는 점이다. 스레드가 노드 0에서 메모리를 잡아뒀는데 노드 1로 이사가면, 그때부터 모든 메모리 접근이 원격이 된다. 그러면 CPU는 놀지 않는데 메모리 대기로 성능이 새어나간다.
# NUMA 구성 확인: 노드별 코어와 노드 간 거리
numactl --hardware
# 예시 출력 (거리 10=로컬, 21=원격)
# node 0 1
# 0: 10 21
# 1: 21 10
# 특정 프로세스를 노드 0의 코어와 메모리에 묶어 실행
numactl --cpunodebind=0 --membind=0 ./my_server
--membind로 메모리를, --cpunodebind로 코어를 같은 노드에 고정하면 원격 접근이 사라진다. 대형 DB들이 실행 옵션에 NUMA 바인딩을 노출하는 이유다.
hugepages: 주소 변환의 지름길
가상 주소를 물리 주소로 바꾸는 표는 TLB라는 작은 캐시에 담긴다. 기본 페이지가 4KB라, 수십 GB 메모리를 쓰는 프로세스는 페이지가 수백만 개가 되고 TLB가 감당을 못 해 TLB 미스가 잦아진다. 미스마다 페이지 테이블을 걸어 내려가는 비용이 든다.
hugepages는 한 페이지를 2MB(혹은 1GB)로 키운다. 같은 메모리를 훨씬 적은 페이지로 덮으니 TLB 엔트리 하나가 넓은 영역을 커버하고, 미스가 급감한다. 큰 힙을 쓰는 DB·JVM·가상머신에서 효과가 크다.
| 구분 | 일반 4KB 페이지 | 2MB hugepage |
|---|---|---|
| 64GB 커버 페이지 수 | 약 1,600만 개 | 약 3.2만 개 |
| TLB 압박 | 높음 | 낮음 |
| 스왑 대상 | 가능 | 보통 고정(스왑 안 됨) |
| 단편화 | 덜함 | 연속 공간 필요 |
THP는 조심스럽게
리눅스에는 hugepage를 자동으로 붙여주는 THP(Transparent Huge Pages)가 있다. 편하지만, 큰 연속 공간을 확보·병합하려는 커널 작업이 예측 못 할 순간에 지연 스파이크를 일으킨다. 그래서 여러 DB 벤더가 THP를 끄고, 필요한 만큼만 명시적으로 예약하는 방식을 권한다.
# 명시적 hugepage 예약 (2MB 페이지 1024개 = 2GB)
sysctl vm.nr_hugepages=1024
# 현재 상태 확인
grep Huge /proc/meminfo
# THP 상태 확인 / 끄기
cat /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/enabled
내가 데인 건 THP였다. 평균 지연은 좋은데 가끔 툭툭 튀는 p99를 못 잡아 며칠을 헤맸다. 범인은 THP의 백그라운드 병합이었고, THP를 never로 끄자 꼬리 지연이 잠잠해졌다. 처리량 지표만 보면 절대 안 보이는 함정이다.
정리: 언제 손대나
- 단일 소켓·소형 서버 — NUMA는 신경 쓸 필요 없다. hugepages도 대개 불필요하다.
- 멀티소켓에서 대형 DB·캐시 — NUMA 바인딩으로 원격 접근을 줄이는 게 1순위다.
- 수십 GB 힙을 쓰는 워크로드 — 명시적 hugepages로 TLB 미스를 줄이고, 지연에 민감하면 THP는 끄는 걸 검토하라.
- 무엇이든 측정 먼저. 벤치 없이 튜닝 플래그부터 켜는 건 도박이다.
자주 묻는 질문
NUMA 바인딩을 하면 항상 빨라지나요?
메모리 접근이 로컬로 유지되면 빨라지지만, 노드에 코어를 고정하면 전체 코어를 자유롭게 쓰지 못해 로드 밸런스가 나빠질 수 있습니다. 워크로드가 한 노드 용량에 맞고 메모리 민감할 때 효과가 큽니다. 반드시 측정 후 판단하세요.
hugepages를 켜면 메모리를 더 쓰나요?
명시적으로 예약한 hugepages는 다른 용도로 못 쓰게 통째로 잡아둡니다. 그래서 과하게 예약하면 일반 메모리가 부족해질 수 있습니다. 애플리케이션이 실제로 요구하는 만큼만 잡는 게 중요합니다.
THP는 그냥 켜두면 안 되나요?
처리량 위주 워크로드에는 도움이 되기도 합니다. 다만 데이터베이스처럼 꼬리 지연(p99)이 중요한 서비스에서는 THP의 백그라운드 병합이 간헐적 지연을 유발할 수 있어, 여러 DB가 비활성화를 권장합니다.
내 서버가 NUMA인지 어떻게 아나요?
numactl --hardware를 실행해 노드가 2개 이상 나오면 NUMA 구조입니다. 노드가 하나뿐이면 단일 소켓이거나 UMA로 동작하는 것이므로 NUMA 튜닝은 의미가 없습니다.

댓글 0