NUMA (Non-Uniform Memory Access) 아키텍처
1. UMA vs NUMA
UMA (Uniform Memory Access)
- 모든 CPU가 하나의 공유 메모리에 동일한 접근 시간으로 접근하는 구조
- 구조가 단순하고 프로그래밍이 쉬움
- CPU 수가 늘어나면 메모리 버스에 병목 발생
- 소규모 시스템(데스크탑, 노트북, Raspberry Pi 등)에서 사용
NUMA (Non-Uniform Memory Access)
- 각 CPU(또는 CPU 그룹)가 자기만의 로컬 메모리를 보유
- 다른 CPU의 메모리에도 접근 가능하지만, 인터커넥트를 경유하므로 시간이 더 걸림
- 대규모 멀티소켓 서버에서 확장성을 확보하기 위해 사용
접근 구조 비교
UMA: CPU → 공유 버스 → 메모리 (모든 CPU가 동일 지연시간)
NUMA: CPU₁ → 로컬 메모리₁ (빠름) / CPU₁ → 원격 메모리₂ (느림, 인터커넥트 경유)
2. NUMA 노드란?
- CPU(또는 CPU 그룹) + 해당 CPU에 직접 연결된 로컬 메모리를 하나의 단위로 묶은 것
- 보통 물리 CPU 소켓 1개 = NUMA 노드 1개
- 단, AMD EPYC 같은 칩렛 구조에서는 하나의 소켓 안에서도 여러 NUMA 노드가 생길 수 있음
- 노드의 기준은 메모리 컨트롤러와 CPU 코어 간의 물리적 접근 거리
3. CPU 소켓 / 코어 / 스레드 계층 구조
계층 의미 lscpu 항목
| 소켓 (Socket) | 메인보드에 CPU를 꽂는 물리적 자리. 소켓 1개 = 물리 CPU 1개 | Socket(s) |
| 코어 (Core) | 하나의 물리 CPU 안에 있는 독립적인 연산 유닛 | Core(s) per socket |
| 스레드 (Thread) | 하이퍼스레딩 지원 시 코어 하나가 논리적으로 2개로 동작 | Thread(s) per core |
예: 2소켓, 소켓당 16코어, 하이퍼스레딩 지원 → 물리 CPU 2개, 코어 32개, 논리 프로세서 64개
4. NUMA 메모리 할당 정책
Local Allocation (기본 정책)
- 프로세스가 실행 중인 CPU의 로컬 노드에서 메모리 할당
- 별도 설정 없으면 기본값
- 지연시간이 가장 짧아 대부분의 경우 최적
Bind
- 지정한 특정 노드에서만 메모리 할당
- 해당 노드 메모리 부족 시 다른 노드로 넘어가지 않고 OOM 발생 가능
- 메모리 위치를 확실히 고정해야 할 때 사용
numactl --membind=0 ./my_app # 노드 0에서만 메모리 할당
Interleave
- 여러 노드에 라운드 로빈 방식으로 페이지를 번갈아 할당
- 특정 노드에 부하가 몰리는 것을 방지
- 메모리 대역폭을 고르게 활용
- 접근 패턴이 불규칙한 공유 데이터(대규모 해시 테이블, DB 버퍼 풀 등)에 유리
numactl --interleave=all ./my_app # 모든 노드에 균등 분산
Preferred
- 지정한 노드에서 우선 할당, 여유 없으면 다른 노드에서 할당
- Bind와 달리 OOM 위험 없이 선호도만 표현하는 유연한 정책
numactl --preferred=1 ./my_app # 노드 1 우선, 부족하면 다른 노드
정책 선택 기준
상황 추천 정책
| 대부분의 일반적인 경우 | Local (기본값) |
| 지연시간에 민감한 실시간 워크로드 | Bind |
| 대역폭이 중요하거나 접근 패턴이 불규칙 | Interleave |
| 약간의 선호도만 주고 싶을 때 | Preferred |
5. NUMA 모니터링: numastat
주요 항목
항목 의미
| numa_hit | 의도한 노드에서 메모리 할당 성공한 횟수 |
| numa_miss | 의도한 노드에 메모리 부족으로 다른 노드에서 할당된 횟수 |
| numa_foreign | 다른 노드가 이 노드에서 메모리를 가져간 횟수 (numa_miss의 반대편 관점) |
| interleave_hit | Interleave 정책으로 할당된 페이지 중 로컬 노드에 떨어진 횟수 |
| local_node | 프로세스가 실행 중인 CPU의 로컬 노드에서 할당된 횟수 |
| other_node | 원격 노드에서 할당된 횟수 |
단일 노드 시스템 예시 (WSL 환경)
node0
numa_hit 14946472
numa_miss 0
numa_foreign 0
interleave_hit 1298
local_node 14946472
other_node 0
- 노드가 1개뿐이므로 miss, foreign, other_node가 모두 0
- interleave_hit이 소량 존재하는 이유: 커널이 부팅 초기에 일부 자료구조를 interleave로 할당하기 때문
numastat이 free보다 유용한 경우
- free는 시스템 전체 메모리 총량만 표시
- numastat은 노드별 메모리 사용량을 분리 표시
- 전체적으로 메모리 여유가 있어도, 특정 노드에 메모리가 몰려 해당 노드에서 swap이 발생하는 상황을 numastat으로 감지 가능
- numastat -m: 노드별 메모리 사용량을 MB 단위로 확인
- numastat -p <PID>: 특정 프로세스의 NUMA 메모리 분포 확인
6. 리눅스 커널과 NUMA
- 리눅스 커널은 NUMA를 항상 지원하지만, 실제로 의미 있게 동작하는지는 하드웨어에 달려있음
- 커널 부팅 시 ACPI의 SRAT 테이블 등에서 NUMA 토폴로지 정보를 읽음
- numactl --hardware로 NUMA 토폴로지 확인 가능
NUMA가 실질적으로 동작하는 경우
- 멀티소켓 서버 (Intel Xeon 2소켓 이상, AMD EPYC 등)
- 일부 데스크탑 CPU (AMD Threadripper 등 칩렛 구조에서 노드가 여러 개로 잡히는 경우)
커널은 지원하지만 단일 노드(=UMA)로 동작하는 경우
- 일반 데스크탑/노트북 (Intel Core, AMD Ryzen)
- SBC (Raspberry Pi)
- WSL 환경