컴퓨터 구조 운영체제 네트워크 자료구조 알고리즘 데이터베이스 내 학습
arch · 16

NUMA 아키텍처

메모리멀티프로세서NUMA

서버에는 CPU 소켓이 둘 이상 달린 경우가 많고, 소켓마다 자기 메모리가 직접 붙어 있습니다. CPU가 자기에게 붙은 메모리(로컬 메모리)를 읽을 때는 빠르지만, 다른 소켓에 붙은 메모리(원격 메모리)를 읽으려면 소켓 사이의 연결(인터커넥트)을 거쳐야 해서 더 오래 걸립니다. 이렇게 메모리가 어디에 있느냐에 따라 접근 시간이 달라지는 구조가 NUMA(Non-Uniform Memory Access)입니다. 프로그램에서는 모든 메모리가 하나의 주소 공간으로 보이지만, 성능은 데이터가 어느 노드에 있는지에 영향을 받습니다.

반대 개념이 UMA(Uniform Memory Access)입니다. 모든 프로세서가 같은 메모리를 같은 지연으로 접근하는 구조입니다. 프로세서가 늘어나면 하나의 메모리 경로를 나눠 써야 해서 병목이 생기므로, 메모리를 여러 개로 나눠 프로세서에 직접 붙이는 NUMA 구조가 서버에서 쓰입니다. 운영체제는 CPU와 그 CPU에 붙은 메모리를 묶어 노드(node)라고 부르고, 데이터를 쓰는 스레드와 같은 노드에 두는 것이 성능의 핵심입니다.

핵심 개념

UMA와 NUMA

UMA는 모든 프로세서가 메모리를 같은 지연으로 접근하는 구조이고, NUMA는 메모리를 노드별로 나눠 붙여 접근 지연이 위치에 따라 달라지는 구조입니다. 교재는 메모리를 프로세서 쪽에 물리적으로 나눠 둔 분산 공유 메모리(DSM) 구조를 NUMA라고도 부른다고 설명합니다.

핵심 개념

노드

CPU(코어들)와 그에 직접 연결된 로컬 메모리를 묶은 단위입니다. 리눅스에서 노드 목록은 numactl --hardware 같은 명령으로 확인합니다. 노드 수가 반드시 소켓 수와 같지는 않습니다.

핵심 개념

로컬 접근과 원격 접근

자기 노드의 메모리를 읽는 것이 로컬 접근이고, 다른 노드의 메모리를 읽는 것이 원격 접근입니다. 원격 접근은 인터커넥트를 지나 상대 노드의 메모리까지 요청이 전달되므로 로컬보다 지연이 큽니다.

핵심 개념

노드 간 거리

노드 사이의 상대적인 접근 비용을 거리(distance) 값으로 표시합니다. numactl --hardware 출력에서 자기 자신까지의 거리는 10이고 다른 노드는 그보다 큰 값입니다. 거리는 상대값이지 측정한 지연이 아닙니다. 이 시각화는 로컬 10, 원격 21이라는 예시 값을 거리 그대로 비용으로 가정합니다.

핵심 개념

메모리 배치 정책

리눅스의 기본 정책은 메모리를 요청한 CPU가 속한 노드에서 할당하는 것(로컬 할당)이고, 익명 메모리는 페이지를 처음 접근(page fault)하는 시점에 실제로 할당됩니다. 정책을 바꿔 특정 노드에만 할당하거나(bind) 페이지 단위로 여러 노드에 돌아가며 할당(interleave)할 수 있고, numactl로 지정합니다.

핵심 개념

자동 NUMA 밸런싱

리눅스 커널에는 접근 패턴을 보고 페이지나 스레드를 옮겨 로컬 접근을 늘리려는 자동 NUMA 밸런싱 기능이 있고 kernel.numa_balancing 설정으로 켜고 끕니다.

NUMA는 확장성을 위한 구조입니다 — 메모리를 노드마다 따로 붙이면 접근이 노드에 고르게 분산될 때 메모리 경로가 노드 수만큼 늘어 전체 대역폭이 커질 수 있고, 한 메모리에 접근이 몰리는 병목이 줄어듭니다.
비용은 원격 접근입니다 — 스레드와 데이터가 다른 노드에 있으면 접근마다 인터커넥트를 지나므로 지연이 늘고, 인터커넥트 대역폭도 소비합니다.
스레드와 데이터를 같은 노드에 둡니다 — 페이지를 처음 접근하는 순간 그 CPU가 속한 노드에 놓이는 기본 정책을 이용하고, 스레드가 다른 노드로 옮겨 다니지 않게 고정하면 로컬 접근을 유지할 수 있습니다.
한 노드에 몰리면 오히려 느려질 수 있습니다 — 모든 데이터가 한 노드에 있으면 다른 노드의 CPU까지 그 노드의 메모리 대역폭과 인터커넥트에 몰려 병목이 될 수 있습니다. 여러 노드가 같은 데이터를 쓰는 경우에는 인터리브로 부하를 나누는 선택이 있습니다.
이 시각화에서 확인할 것 — "UMA vs NUMA" 탭에서 두 구조의 차이와 로컬·원격 경로를, "로컬 vs 원격 비용" 탭에서 같은 접근 횟수의 총 거리 차이를, "배치와 이동" 탭에서 스레드 이동과 배치 정책에 따라 평균 거리가 달라지는 과정을 STEP으로 확인하세요.

NUMA의 배경

프로세서 수가 적을 때는 하나의 공용 메모리를 모두가 같은 지연으로 나눠 쓰는 형태(UMA, SMP)가 흔했습니다. 프로세서 수가 늘면 그 메모리 경로가 병목이 되므로, 메모리를 프로세서 쪽으로 나눠 붙이고 필요하면 서로의 메모리를 접근하게 하는 구조가 나왔습니다. 메모리 컨트롤러가 CPU 칩에 들어 있어 소켓이 둘 이상인 서버는 보통 소켓마다 로컬 메모리를 가진 NUMA 구조가 됩니다.

숫자로 확인하기

로컬과 원격의 총 거리 — 거리를 로컬 10, 원격 21이라고 하면 같은 데이터를 100번 읽을 때 로컬은 10 × 100 = 1,000, 원격은 21 × 100 = 2,100이라 원격이 2.1배입니다. 거리를 비용으로 본 단순 모델이며 실제 비율은 하드웨어마다 다릅니다.
인터리브의 평균 — 페이지 4개를 노드 0, 1, 0, 1에 번갈아 두고 노드 0의 스레드가 균등하게 읽으면 평균 거리는 (10 + 21 + 10 + 21) ÷ 4 = 15.5이고 원격 접근 비율은 50%입니다.
스레드 이동의 영향 — 데이터가 노드 0에 있는데 스레드가 노드 1로 옮겨 가면 평균 거리가 10에서 21로 바뀌고 모든 접근이 원격이 됩니다.

자주 하는 오해

원격 메모리는 네트워크나 디스크에 있는 메모리다 — 같은 컴퓨터 안의 다른 소켓에 붙은 메모리입니다. 같은 주소 공간으로 일반 로드·스토어 명령으로 접근하고, 단지 더 먼 경로를 거칩니다.
NUMA는 운영체제가 알아서 최적화하니 신경 쓰지 않아도 된다 — 기본 정책과 자동 밸런싱이 도움이 되지만, 스레드가 다른 노드로 옮겨 가거나 메모리를 한 스레드가 할당해 다른 스레드가 쓰는 경우에는 원격 접근이 늘 수 있습니다.
소켓 하나가 노드 하나다 — 항상 그렇지는 않습니다. 소켓 하나를 여러 노드로 나눠 보이게 하는 설정이 있는 CPU도 있으므로 노드 구성은 numactl --hardware 같은 명령으로 확인합니다.
NUMA에서는 메모리를 아무 데나 써도 속도가 같다 — 이름 그대로 접근 시간이 균일하지 않습니다. 같은 프로그램도 데이터와 스레드가 어느 노드에 있느냐에 따라 시간이 달라집니다.

면접에서 이렇게 물어요

면접 질문

NUMA와 UMA의 차이는 무엇인가요?

UMA는 모든 프로세서가 메모리를 같은 지연으로 접근하고, NUMA는 메모리가 노드별로 나뉘어 로컬 접근이 원격 접근보다 빠릅니다. NUMA는 메모리 경로가 노드 수만큼 늘어 대규모 시스템에 알맞습니다.

면접 질문

원격 메모리 접근이 왜 느린가요?

다른 노드의 메모리를 읽으려면 요청이 노드 사이의 인터커넥트를 지나 상대 노드의 메모리 컨트롤러까지 전달되기 때문입니다. 경로가 길고 인터커넥트 대역폭도 나눠 써야 합니다.

면접 질문

NUMA에서 성능을 높이는 방법은?

스레드와 그 스레드가 쓰는 데이터를 같은 노드에 둡니다. 스레드를 노드에 고정하고, 데이터를 처음 쓰는 스레드가 있는 노드에 할당되게 하며, 여러 노드가 공유하는 데이터는 인터리브로 분산합니다.

면접 질문

리눅스에서 NUMA 구성은 어떻게 확인하나요?

numactl --hardware로 노드, 노드별 메모리, 노드 간 거리를 볼 수 있고, numastat로 노드별 메모리 할당 통계를 볼 수 있습니다. 프로세스는 numactl로 노드 고정과 메모리 정책을 지정해 실행합니다.

실제 시스템에서는

리눅스 메모리 정책 — 리눅스 커널 문서는 NUMA 메모리 정책을 설명하며, 기본 정책이 요청 CPU의 노드에 할당하는 로컬 할당이고 interleave 정책은 페이지 단위로 노드들에 돌아가며 할당한다고 합니다. numactl 매뉴얼은 --cpunodebind, --membind, --interleave 옵션을 설명합니다.
JVM — OpenJDK의 JEP 345는 G1 가비지 컬렉터가 NUMA를 인식해 메모리를 할당하도록 하는 변경을 소개합니다. -XX:+UseNUMA 옵션으로 켭니다.
메모리와의 관계 — NUMA는 메모리 접근 지연이 위치에 따라 다른 구조이므로 메모리 계층의 지연 차이 논의와 이어집니다.

함께 보면 좋은 토픽

메모리 계층 — 가까운 메모리가 빠르다는 원리를 봅니다.
캐시 메모리 — 메모리 접근 지연을 줄이는 캐시의 동작을 봅니다.
메모리 관리 — 운영체제가 메모리를 어떻게 할당하는지 봅니다.
CPU 스케줄링 — 스레드를 어느 CPU에서 실행할지 정하는 방식을 봅니다.
스레드 — 스레드가 같은 주소 공간을 공유하는 모델을 봅니다.

참고 자료

교재 — Hennessy, Patterson, "Computer Architecture: A Quantitative Approach", "Thread-Level Parallelism" 장

NUMA 구성 확인 — numactl과 numastat

# 노드, 노드별 메모리, 노드 간 거리를 출력
numactl --hardware

# 출력 예(노드 2개): 자기 자신은 10, 다른 노드는 더 큰 값
# node distances:
# node   0   1
#   0:  10  21
#   1:  21  10

# 노드별 메모리 사용 통계
numastat

numactl --hardware는 시스템의 노드 수, 노드마다 붙은 메모리 크기, 노드 사이의 거리를 보여 줍니다. 거리 행렬에서 대각선(자기 자신)은 10이고 값이 클수록 멀다는 뜻이므로, 이 시각화의 로컬 10, 원격 21은 그런 출력의 예시 값입니다. 실제 값은 하드웨어마다 다르고 노드가 하나뿐인 시스템에서는 거리 행렬이 한 칸입니다. numastat은 노드별로 의도한 노드에서 할당된 횟수(numa_hit)와 다른 노드에서 할당된 횟수(numa_miss, other_node)를 보여 줍니다. 접근 횟수가 아니라 할당 횟수이므로, 메모리가 의도한 노드에 놓이고 있는지 가늠할 때 확인합니다.

스레드와 메모리를 같은 노드에 고정해 실행

# 노드 0의 CPU에서만 실행하고, 메모리도 노드 0에서만 할당
numactl --cpunodebind=0 --membind=0 ./server

# 메모리를 모든 노드에 번갈아 할당(인터리브)
numactl --interleave=all ./server

첫 명령은 프로세스를 노드 0의 CPU에 묶고 메모리도 노드 0에서만 받게 해서 모든 접근을 로컬로 유지합니다. 다만 노드 0의 메모리가 부족하면 할당이 실패할 수 있어, 데이터가 그 노드에 들어가는 크기인지 확인해야 합니다. 둘째 명령은 페이지를 노드에 번갈아 배치해 한 노드에 접근이 몰리는 것을 막습니다. 접근의 일부는 원격이 되므로, 여러 노드가 같은 큰 데이터를 쓰는 서버에서 선택하는 방식입니다. 어느 쪽이 빠른지는 작업 패턴에 달려 있으므로 실제 워크로드로 측정해 정해야 합니다.

JVM에서 NUMA 인식 켜기

# G1 가비지 컬렉터가 NUMA를 인식해 메모리를 할당하도록 함
java -XX:+UseNUMA -XX:+UseG1GC -jar app.jar

-XX:+UseNUMA는 JVM이 힙을 NUMA 노드에 맞춰 할당하도록 하는 옵션이고, JEP 345가 G1에서 이를 지원하도록 한 변경입니다. 스레드가 객체를 만들 때 그 스레드가 실행되는 노드의 메모리에서 할당하려는 목적입니다. 노드가 하나뿐인 시스템에서는 이점이 없습니다(JEP는 NUMA 인식을 끈 경우와 성능이 같아야 한다고 명시합니다). 이 옵션이 이득인지는 애플리케이션의 메모리 접근 패턴과 하드웨어에 따라 다르므로 켜기 전후를 측정해 비교해야 합니다.

Q1

NUMA(Non-Uniform Memory Access)에서 "Non-Uniform"이 가리키는 것은?

"균일하지 않은 메모리 접근"이라는 이름 그대로입니다.
NUMA에서는 프로세서에 붙어 있는 로컬 메모리 접근은 빠르고, 다른 노드에 붙은 원격 메모리 접근은 더 느립니다. 이처럼 접근 시간이 균일하지 않다는 뜻입니다.

Q2

NUMA에서 원격 메모리 접근이 로컬 메모리 접근보다 느린 이유는?

메모리가 다른 프로세서 소켓에 붙어 있습니다.
원격 노드의 메모리를 읽으려면 프로세서 간 인터커넥트를 통해 상대 노드의 메모리 컨트롤러까지 요청이 전달되어야 하므로 로컬 접근보다 지연이 큽니다.

Q3

NUMA 환경에서 성능을 높이기 위한 방법으로 알맞은 것은?

가까운 메모리를 쓰면 지연이 줄어듭니다.
스레드와 그 스레드가 쓰는 메모리를 같은 NUMA 노드에 두면 원격 접근이 줄어 지연이 감소합니다. 운영체제의 NUMA 인식 스케줄링이나 numactl 같은 도구가 이를 돕습니다.