같은 하드웨어, 같은 VM 사양인데 성능 차이가 크게 나는 이유 중 하나가 NUMA 정렬 실패입니다. 호스팅 사업자라면 알아야 하고, VPS 사용자도 호스트 측이 잘 했는지 의심해볼 가치가 있는 주제입니다.
NUMA란
NUMA(Non-Uniform Memory Access)는 멀티 소켓 서버에서 각 CPU가 자신과 가까운 메모리에 더 빠르게 접근하는 구조입니다.
NUMA Node 0 (Socket 0): CPU 0~15 + DDR 슬롯 절반 (128GB)
NUMA Node 1 (Socket 1): CPU 16~31 + DDR 슬롯 절반 (128GB)
두 노드는 UPI(Intel) / Infinity Fabric(AMD)으로 연결
같은 노드 내 메모리 접근은 ~80ns, 다른 노드 메모리 접근은 ~140ns (1.5~2배 느림). NIC·NVMe 같은 PCIe 디바이스도 특정 노드에 붙어 있어, 잘못 매핑되면 cross-node 접근이 됩니다.
NUMA 토폴로지 확인
# 전체 토폴로지
lscpu | grep -i numa
# NUMA node(s): 2
# NUMA node0 CPU(s): 0-15
# NUMA node1 CPU(s): 16-31
# 자세한 정보
numactl --hardware
# node 0 cpus: 0 1 2 ... 15
# node 0 size: 130984 MB
# node 1 cpus: 16 17 ... 31
# node 1 size: 130984 MB
# node distances:
# node 0 1
# 0: 10 21 # 같은 노드 10 = 빠름
# 1: 21 10 # 다른 노드 21 = 느림
node distances의 숫자가 작을수록 빠릅니다. 자기 노드 10, 다른 노드 21 — 약 2배 차이가 메모리 접근 시 발생합니다.
잘못 매핑된 VM의 증상
NUMA 정렬을 안 맞춘 VM은 다음 증상을 보입니다.
- DB 쿼리 응답 시간 2~3배
- 인터럽트가 한 코어에 몰림
- 메모리 대역폭이 절반 이하
- L3 캐시 미스 증가
- 컨텍스트 스위칭 비용 증가
perf stat 출력에서 LLC-load-misses가 비정상적으로 높거나, numastat에서 numa_miss가 증가하면 의심.
# VM 프로세스의 NUMA 통계
numastat -p $(pidof qemu-system-x86_64)
# 시스템 전체
numastat -m
CPU 핀이란
vCPU(가상 CPU)를 특정 물리 CPU에 고정하는 것입니다. 운영체제가 임의로 vCPU를 옮기지 않게 합니다.
기본 동작 (핀 없음):
vCPU → 어떤 물리 코어든 사용 가능 (스케줄러 결정)
→ 캐시 hit율 떨어짐
→ NUMA 노드 가로지를 수 있음
핀 적용:
vCPU 0 → 물리 CPU 2 (고정)
vCPU 1 → 물리 CPU 3 (고정)
→ L1·L2 캐시 따뜻하게 유지
→ 같은 NUMA 노드에 묶여 가까운 메모리 사용
libvirt에서 NUMA·CPU 핀 설정
<domain type='kvm'>
<name>db-vm</name>
<memory unit='KiB'>16777216</memory>
<vcpu placement='static' cpuset='2-3'>2</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<emulatorpin cpuset='2-3'/>
</cputune>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
<cpu mode='host-passthrough'>
<topology sockets='1' cores='2' threads='1'/>
<numa>
<cell id='0' cpus='0-1' memory='16777216' unit='KiB'/>
</numa>
</cpu>
...
</domain>
핵심 항목:
cpuset='2-3': vCPU가 사용할 물리 CPU 집합vcpupin: 각 vCPU를 특정 물리 CPU에 고정numatune memory mode='strict' nodeset='0': 메모리도 NUMA Node 0에서만emulatorpin: QEMU 자체 스레드도 같은 곳에
mode='strict'는 다른 노드 메모리 사용 절대 금지. preferred는 가능하면 선호.
모범 사례 — VM 크기별 권장 설정
작은 VM (1~4 vCPU, 4~16GB RAM)
한 NUMA 노드에 완전히 들어가게 핀:
<vcpu placement='static' cpuset='2-5'>4</vcpu>
<numatune>
<memory mode='strict' nodeset='0'/>
</numatune>
중간 VM (8~16 vCPU, 32~64GB RAM)
여전히 한 노드 내. 노드 한 곳의 CPU·메모리로 충분하면 그게 최선.
큰 VM (한 노드보다 큼)
어쩔 수 없이 두 노드 사용. 게스트 OS에게 vNUMA 토폴로지를 보여줘 게스트도 NUMA 인지하게:
<cpu mode='host-passthrough'>
<topology sockets='2' cores='8' threads='1'/>
<numa>
<cell id='0' cpus='0-7' memory='32GB'/>
<cell id='1' cpus='8-15' memory='32GB'/>
</numa>
</cpu>
<numatune>
<memory mode='strict' nodeset='0,1'/>
<memnode cellid='0' mode='strict' nodeset='0'/>
<memnode cellid='1' mode='strict' nodeset='1'/>
</numatune>
NIC/NVMe NUMA 위치 확인
PCIe 디바이스도 특정 노드에 연결되어 있습니다.
# eth1 NIC의 NUMA 노드
cat /sys/class/net/eth1/device/numa_node
# 0 → Node 0에 연결
# NVMe의 NUMA 노드
cat /sys/class/nvme/nvme0/device/numa_node
# 1 → Node 1에 연결
VM이 사용하는 NIC·디스크가 어느 노드에 있는지 확인하고 VM의 핀도 그 노드로 정렬:
- NIC가 Node 0이면 → VM도 Node 0
- NVMe가 Node 1이면 → VM도 Node 1
NIC·디스크가 다른 노드라면 둘 중 더 중요한 것에 맞춰 핀하거나, 가능하면 PCIe 슬롯을 재배치.
인터럽트 친화도 (IRQ Affinity)
NIC 인터럽트도 특정 코어에서 처리되게 고정:
# NIC 인터럽트 번호 확인
grep eth1 /proc/interrupts
# 인터럽트를 특정 CPU에 고정
echo 8 > /proc/irq/47/smp_affinity # CPU 3에 고정 (bitmask)
VM의 vCPU와 인터럽트 처리 CPU가 같은 NUMA 노드여야 효과적입니다. irqbalance가 자동으로 분산하지만, 수동 고정이 더 정확.
Huge Pages와 NUMA 결합
큰 워크로드는 huge page를 NUMA 노드별로 미리 예약:
# Node 0에 2048개 (4GB), Node 1에 2048개
echo 2048 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 2048 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# 확인
numastat -m | grep -i hugepage
VM에서 huge page를 쓰면서 NUMA 핀까지 맞추면 메모리 접근이 매우 빨라집니다.
검증 — 실제 효과 측정
핀 적용 전후 비교:
# 단순 메모리 대역폭 측정 (게스트 안에서)
sysbench memory --memory-block-size=1M --memory-total-size=10G run
# DB 워크로드 (sysbench OLTP)
sysbench oltp_read_only --mysql-host=... run
기대 효과:
- 잘못된 NUMA 매핑 → 핀 적용: 20~50% 성능 향상
- 이미 정렬되어 있던 환경: 5~10% 향상
장점이 명확하면 호스팅 운영에 표준 적용. 큰 차이 없으면 다른 병목 점검.
함정과 주의점
1) 핀이 너무 빡빡하면 호스트 유연성 손실
모든 VM을 완전 핀하면 호스트 스케줄러가 부하 분산을 못 함. 호스팅 사업자는 VM 클래스별 정책으로 일부만 핀(예: DB·게임 서버).
2) 라이브 마이그레이션 시 핀 재설정
다른 호스트로 옮기면 그 호스트의 CPU 번호가 다르므로 핀 정보도 함께 옮겨가야 함.
3) 호스트 하드웨어 변경
NIC·디스크의 PCIe 슬롯이 바뀌면 NUMA 위치도 바뀜. 변경 후 재확인 필수.
4) AutoNUMA의 도움·방해
커널의 AutoNUMA가 자동으로 NUMA를 맞춰주는 경우도 있지만, 명시적 핀이 있으면 더 안정적.
# AutoNUMA 끄기 (명시적 핀을 100% 신뢰)
echo 0 > /proc/sys/kernel/numa_balancing
VPS 사용자 입장에서 확인
VPS 안에서도 NUMA 정렬이 잘 되었는지 부분적으로 확인 가능:
# 게스트가 보는 NUMA
numactl --hardware
# 좋음: node 0 하나만 (호스트가 정렬해 줌)
# 나쁨: node 0/1 두 개에 메모리 분산
게스트 안에서 메모리 대역폭이 일정한지, 특정 코어 지연이 큰지 측정하면 호스트 NUMA 매핑이 잘 됐는지 간접적으로 알 수 있습니다.
마무리
NUMA·CPU 핀은 호스팅 사업자가 차별화할 수 있는 “보이지 않는 품질”입니다. 같은 사양의 VPS인데 누구는 빠르고 누구는 느린 이유 중 하나가 여기 있습니다.
TCP-80.NET의 VPS·전용서버는 호스트 측 NUMA·인터럽트 정렬을 표준 적용해 출고됩니다. 자체 KVM 환경에서 튜닝 가이드가 필요하시면 @tcp80net으로 문의해 주세요.