NUMA (Non-Uniform Memory Access) 기반 스케줄링

핵심 인사이트 (3줄 요약)

  1. 본질: NUMA (불균일 메모리 접근) 아키텍처는 다수의 코어가 하나의 거대한 메모리를 공유하는 SMP의 병목을 부수기 위해, 물리적인 CPU 칩(소켓)마다 전용 로컬 메모리(Local Memory)를 묶어 '노드(Node)'로 분리한 최신 엔터프라이즈 하드웨어 구조다.
  2. 가치: 스케줄러가 아무 코어에나 프로세스를 던지는 멍청한 로드 밸런싱을 멈추고, "해당 프로세스의 데이터가 저장된 메모리와 가장 가까운 CPU 노드"에 영구적으로 스케줄링(NUMA-aware)함으로써 메모리 접근 속도를 비약적으로 끌어올린다.
  3. 융합: 멀티코어 환경의 캐시 친화도(Affinity)를 시스템 메모리 대역폭 차원까지 확장시킨 기술로, 클라우드 가상 머신(VM) 및 초대형 데이터베이스(Oracle, SAP HANA) 성능 튜닝에서 반드시 정복해야 할 인프라 아키텍처의 정점이다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: "Non-Uniform (불균일하다)"이라는 이름처럼, CPU 코어가 메모리의 어떤 번지에 접근하느냐에 따라 메모리 응답 속도(Latency)가 다르다는 물리적 현실을 반영한 아키텍처다. 내 동네(로컬 노드) 메모리는 빠르고, 옆 동네(리모트 노드) 메모리는 느리다.
  • 필요성: SMP (대칭형 다중 처리) 아키텍처는 수십 개의 코어가 오직 1개의 버스(Bus)를 통해 1개의 메인 메모리로 접근하려다 보니 교통체증(대역폭 병목)으로 폭발해 버렸다. 이 한계를 극복하려면 CPU 여러 개를 메인보드에 꽂을 때(Dual Socket 등), 각 CPU 옆에 전용 메모리를 따로 달아주어 자기 밥그릇(로컬 메모리)부터 파먹게 만드는 혁신적 분산 구조가 필요했다.
  • 💡 비유: 100명의 학생이 식당 1곳(SMP)으로 몰려가서 밥을 먹다 깔려 죽는 대신, 4개의 건물(NUMA 노드)에 식당 4곳을 분산 배치하고 가급적 자기가 수업 듣는 건물 식당에서 밥을 먹도록(로컬 메모리 접근) 유도하는 효율적인 캠퍼스 설계와 같다.
  • 등장 배경: 서버용 CPU(인텔 제온, AMD 에픽) 코어 수가 32개, 64개를 훌쩍 넘어가면서 기존 UMA(Uniform Memory Access) 구조로는 시스템 버스 트래픽을 감당할 수 없었다. 2000년대 후반부터 하드웨어 단에서 NUMA가 표준으로 채택되었고, 운영체제(Linux 커널)는 이에 발맞춰 'NUMA를 인지하는 똑똑한 스케줄러(NUMA-aware Scheduler)'로 진화해야만 했다.
  [기존 UMA (SMP) vs 현대 NUMA 아키텍처 비교]

  (1) 과거 UMA (균일 접근): 거대한 병목 발생
  [코어0][코어1][코어2][코어3]
      │   │   │                                                      │
      ▼   ▼   ▼   ▼ ──▶ 단일 버스(Bus) 교통 체증 💥
   [ 거대 단일 공유 메모리 (RAM) ] 

  (2) 현대 NUMA (불균일 접근): 지역성(Locality) 기반의 초고속망
  ┌────── NUMA 노드 0 ──────┐          ┌────── NUMA 노드 1 ──────────┐
  │ [코어0] [코어1] [코어2]   │  QPI 망  │ [코어3] [코어4] [코어5]   │
  │   │ 로컬 접근 (초고속!)    │ ◀────▶ │   │ 로컬 접근 (초고속!)    │
  │   ▼                     │ (교각) │   ▼                           │
  │ [ 로컬 메모리 RAM 0 ]     │          │ [ 로컬 메모리 RAM 1 ]     │
  └─────────────────────────┘          └─────────────────────────────┘
  
  * 🚨 문제: 코어0이 옆 동네 'RAM 1'에 접근(Remote Access)하려면 
             느린 QPI 교각을 건너야 하므로 성능이 30% 폭락한다.

[다이어그램 해설] NUMA의 철학은 '로컬(내 구역)은 미친 듯이 빠르고, 리모트(남의 구역)는 더럽게 느리다'는 불평등의 인정이다. 따라서 운영체제 스케줄러의 목표는 명확해진다. "제발 프로세스를 딴 동네(노드)로 이사시키지 마라!" 과거엔 코어끼리 일감(프로세스)을 뺏어오는 로드 밸런싱이 미덕이었지만, NUMA 시대에서는 섣부른 이사가 "데이터와 코어의 생이별"을 낳아 서버 성능을 마비시키는 최악의 안티패턴이 되었다.

  • 📢 섹션 요약 비유: 부산에서 일하는 직원(코어0)의 서류철(데이터)이 부산 지사(RAM 0)에 있으면 1초 만에 찾지만, 멍청한 사장(OS)이 직원을 서울 지사(코어3)로 발령 내면, 그 직원은 일할 때마다 KTX(QPI 버스)를 타고 부산까지 와서 서류를 꺼내 가야 하므로 업무 속도(Remote Access)가 바닥을 칩니다.

Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

NUMA-Aware 스케줄러의 3대 동작 원칙

운영체제(Linux 커널 등)는 하드웨어의 NUMA 토폴로지를 읽어 들인 뒤, 철저한 지역성(Locality) 위주로 스케줄링을 재편한다.

  1. 메모리 지역성 할당 (First-touch Allocation)
    • 프로세스가 처음 메모리를 요청(malloc)하고 데이터를 쓸 때, OS는 해당 프로세스가 돌고 있는 현재 CPU 코어가 속한 NUMA 노드의 로컬 메모리에서 물리적 RAM을 잘라준다.
    • 즉, "코어가 있는 곳에 밥상을 차려준다"는 원칙이다.
  2. 강한 노드 친화도 (Strict Node Affinity)
    • 타 코어로 이주(Migration)가 필요하더라도, 절대 남의 NUMA 노드에 있는 코어로는 프로세스를 옮기지 않는다. 같은 건물(노드 0) 안에 있는 코어 0에서 코어 2로만 옮긴다.
    • 만약 노드 0의 코어들이 터져 나가고 노드 1이 놀고 있어 부득이하게 노드 1로 스케줄링해야 한다면?
  3. 페이지 마이그레이션 (Memory Page Migration) 동반
    • 어쩔 수 없이 프로세스를 노드 1의 코어 3으로 옮겼다면, 커널 데몬(numad 등)이 백그라운드에서 노드 0 메모리에 있던 밥상(Page) 자체를 노드 1 메모리로 무거운 비용을 들여 통째로 이사시켜 버린다. (데이터를 코어가 있는 곳으로 다시 데려옴)

최악의 NUMA 성능 붕괴 시나리오: 핑퐁 (Ping-Pong)

  ┌───────────────────────────────────────────────────────────────────────────┐
  │         멍청한 로드 밸런서가 유발하는 NUMA 핑퐁 장애 시나리오             │
  ├───────────────────────────────────────────────────────────────────────────┤
  │                                                                           │
  │  [상황] 프로세스 P의 데이터는 '노드 0 메모리'에 10GB 저장됨.              │
  │                                                                           │
  │  1초: 커널 밸런서 "노드 0이 바쁘네? P를 노드 1(코어 3)로 이사!"           │
  │       ▶ P는 코어 3에서 실행되지만, 데이터는 노드 0에 있어 느리게 원격 조회│
  │  3초: 커널 밸런서 "노드 1이 바쁘네? P를 다시 노드 0으로 원복!"            │
  │       ▶ P는 다시 로컬로 붙어서 초고속 연산 시작                           │
  │  5초: 또 노드 1로 이사! ─▶ 7초: 또 노드 0으로 이사!                       │
  │                                                                           │
  │  🚨 재앙: 이 프로세스는 연산은 못 하고 남의 동네 교각(QPI 버스)만         │
  │          오가며 트래픽을 폭파시키는 '버스 핑퐁'을 쳐서 서버 전체 멈춤!    │
  └───────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 멀티코어 환경에서 멍청한 스케줄러가 부하의 균형(Load Balance)만 맞추려다 벌어지는 대참사다. NUMA 환경에서는 **"균형이 좀 안 맞더라도, 데이터가 있는 곳에 코어를 뼈 묻게 놔두는 것"**이 전체 스루풋(Throughput) 관점에서는 수백 배 더 이득이다.

  • 📢 섹션 요약 비유: 아이들 방(노드) 균형을 맞춘다고 매일 밤 장난감(데이터)은 큰방에 둔 채 잠자리(코어)만 작은방으로 강제 이사시키면, 아이들은 놀 때마다 거실(버스)을 횡단하며 집안 전체를 난장판으로 만듭니다.

Ⅲ. 융합 비교 및 다각도 분석 (Comparison & Synergy)

메모리 정책: Local Allocation vs Interleave

NUMA 시스템에서 메모리를 할당하는 정책은 크게 두 가지로 나뉘며, 워크로드의 성격에 따라 아키텍트가 직접 결정(numactl 튜닝)해야 한다.

비교 항목1. Local (디폴트, First-touch)2. Interleave (교차 할당)
동작 방식현재 코어가 있는 노드의 메모리에 100% 몰아서 할당전체 노드(노드0, 노드1)의 메모리를 균등하게 50%씩 쪼개서 번갈아 할당
특징/장점단일 스레드가 자기 데이터만 만질 때 극강의 초고속수십 개의 스레드가 거대 데이터를 무작위로 공유 접근할 때 병목 파괴
단점 (장애)특정 노드 메모리만 100% 꽉 차서 OOM(Out of Memory) 터지고 남의 노드 메모리는 텅 비는 스왑(Swap) 장애 유발무조건 절반의 확률로 느린 리모트(Remote) 접근이 발생하여 절대 성능 저하
적용 타깃일반적인 웹 서버 (Nginx), 컨테이너 (Docker)초거대 In-Memory DB (Redis, SAP HANA, Oracle SGA)

데이터베이스의 거대한 공유 버퍼 풀(Buffer Pool)은 수십 개의 코어가 사방에서 접근한다. 만약 이걸 Local로 설정해서 노드 0 메모리에만 100GB를 몰아넣으면, 노드 1 코어들이 쿼리를 때릴 때마다 QPI 버스가 불타버린다. 그래서 DB 서버 엔지니어는 반드시 numactl --interleave=all 옵션을 주어 100GB를 노드 0에 50GB, 노드 1에 50GB 강제 분산시켜 버스 트래픽 부하를 절반으로 쪼개버린다.

  • 📢 섹션 요약 비유: Local 정책은 직원 각자의 책상(노드) 서랍에 서류를 다 몰아주는 것이고(개인 작업에 최적화), Interleave 정책은 서류를 공용 도서관 여러 곳에 흩뿌려 놓아 누구나 접근 거리를 비슷하게 만드는 것(공유 협업에 최적화)입니다.

Ⅳ. 실무 적용 및 기술사적 판단 (Strategy & Decision)

실무 시나리오

  1. 가상 머신(VM) 클라우드 인프라의 vNUMA 매핑 (VMware, KVM): AWS나 프라이빗 클라우드에서 16코어 64GB짜리 대형 가상 머신(VM)을 팔 때, 이 VM을 물리 서버의 노드 0 코어 8개와 노드 1 코어 8개에 걸치게(Span) 쪼개서 할당하는 경우가 생긴다.
    • 치명적 장애: VM 안에 설치된 리눅스 OS는 자기가 1개의 깔끔한 UMA 노드 안에 있는 줄 착각하고 아무렇게나 코어 간 이사를 시키는데, 실제 하드웨어 바닥에서는 물리적 노드 0과 1을 미친 듯이 횡단하는 끔찍한 리모트 엑세스가 발생해 지연율(Latency)이 튀게 된다.
    • 아키텍처 결단: 하이퍼바이저는 반드시 vNUMA (Virtual NUMA) 토폴로지를 VM에게 노출해 주어야 한다. "야, 너 지금 두 동네에 걸쳐 있으니까, 네 안에서도 웬만하면 0~7번 코어끼리만 놀아"라고 VM 커널이 인지(Aware)하게 만들어야 성능 저하를 막을 수 있다.
  2. 리눅스 OOM 스왑(Swap) 절망편 (NUMA Imbalance): 물리 메모리 128GB(노드0 64G, 노드1 64G) 서버에서 메모리 사용량이 70GB밖에 안 되는데 서버가 미친 듯이 버벅이며 디스크 스왑(Swap)을 치는 현상.
    • 원인 분석: numastat을 쳐보면, 프로세스가 노드 0에만 할당되어 노드 0 메모리 64GB를 다 써버렸다. 노드 1 메모리에는 64GB가 텅텅 비어 남아도는데도, 리눅스 디폴트 정책(Node Local) 때문에 "내 노드엔 메모리 없네? 스왑 써야지!"라며 멀쩡한 남의 노드 메모리를 놔두고 디스크를 긁어버린 것이다.
    • 실무 조치: 커널 파라미터 vm.zone_reclaim_mode=0으로 세팅하여 "네 노드 메모리 다 썼으면, 느리더라도 디스크 쓰지 말고 옆 노드 1 메모리 빌려다 써라"라고 정책을 교정해 주어야 이 억울한 스래싱을 막을 수 있다.
  ┌──────────────────────────────────────────────────────────────────┐
  │     NUMA 아키텍처 환경의 성능 최적화(Tuning) 의사결정 트리       │
  ├──────────────────────────────────────────────────────────────────┤
  │                                                                  │
  │   [ 2-Socket (NUMA 노드 2개) 물리 서버에 서비스 배포 ]           │
  │                │                                                 │
  │                ▼ 서비스의 성격 분석                              │
  │   메모리를 대규모로 공유하는 단일 인메모리 시스템인가? (DB/Cache)│
  │          ├─ [예 (Redis, Memcached, Oracle)]                      │
  │          │      │                                                │
  │          │      ▼ 튜닝 조치                                      │
  │          │  - numactl --interleave=all 로 실행                   │
  │          │  - 커널 투명적 거대 페이지(THP) 비활성화 고민         │
  │          │                                                       │
  │          └─ [아니오 (독립적인 Nginx, NodeJS 다수 컨테이너)]      │
  │                 │                                                │
  │                 ▼ 튜닝 조치                                      │
  │             - numactl --cpunodebind=0 --membind=0                │
  │               (A 컨테이너는 완벽히 노드 0번에 철창 격리)         │
  │             - numactl --cpunodebind=1 --membind=1                │
  │               (B 컨테이너는 완벽히 노드 1번에 철창 격리)         │
  └──────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 서버 엔지니어의 핵심 역량이다. 독립적인 웹 서버 10개를 띄울 때는 절대 인터리브(Interleave)를 쓰면 안 된다. 5개는 0번 노드에, 5개는 1번 노드에 완벽하게 CPU와 메모리를 강제 고정(Pinning/Binding)시켜 서로가 옆 동네를 평생 바라보지도 않게 쪼개버리는 것(NUMA Isolation)이 각자의 100% 로컬 초고속 성능을 이끌어내는 마법이다.

  • 📢 섹션 요약 비유: 가족이 살 땐 1층(노드 0)과 2층(노드 1)을 자유롭게 오가게(Interleave) 두어야 화목하지만, 생판 남인 세입자 두 명(도커 컨테이너)을 받을 때는 1층과 2층 사이의 계단을 아예 막아버리고 각자 층에서만 살게(Node Bind) 해야 싸움 없이 완벽하게 독립적인 쾌적한 삶이 보장됩니다.

Ⅴ. 기대효과 및 결론 (Future & Standard)

기대효과

운영체제의 NUMA-Aware 스케줄링과 메모리 배치 최적화를 통해, 엔터프라이즈 시스템은 100코어 이상의 무지막지한 프로세서 환경에서도 시스템 버스 병목 없이 선형적인 성능 향상(Linear Scalability)을 이루어내며 초대형 딥러닝과 인메모리 빅데이터 연산을 소화할 수 있다.

결론 및 미래 전망

NUMA는 과거 슈퍼컴퓨터의 전유물이었으나, 현재는 AMD Threadripper(칩렛 구조)와 Intel Xeon의 대중화로 인해 우리가 쓰는 클라우드의 모든 기본 하드웨어 아키텍처가 되었다. 즉, "멀티코어 프로그래밍 = NUMA 프로그래밍"인 시대다. 미래의 스케줄러와 언어 런타임(Go, Rust)은 개발자가 numactl을 수동으로 입력할 필요 없이, 컴파일러와 커널의 eBPF 추적기가 메모리 접근 패턴을 실시간으로 감시하여 실행 중에 핫 데이터(Hot Data)와 스레드를 스스로 같은 노드로 모아 합쳐주는 극단적인 자율 최적화 NUMA 토폴로지 관리로 진화할 것이다.

  • 📢 섹션 요약 비유: 옛날엔 땅이 좁아서 그냥 뛰어다녀도 됐지만(UMA), 땅이 너무 넓어진 제국(NUMA)에서는 공무원(커널 스케줄러)이 백성(프로세스)과 직장(데이터)의 거리를 계산해 무조건 직장 근처 아파트(로컬 메모리)에 살게 강제 배정하지 않으면, 전 국민이 출퇴근(메모리 엑세스) 하느라 길바닥에서 늙어 죽는 교통지옥이 열립니다.

📌 관련 개념 맵 (Knowledge Graph)

개념 명칭관계 및 시너지 설명
대칭형 다중 처리 (SMP)모든 코어가 하나의 평등한 메모리를 보던 이상적인 시대. 이 방식이 대역폭 병목으로 파괴되면서 NUMA가 태어났다.
프로세서 친화도 (Affinity)스케줄러가 코어 간 이동을 억제하여 L1/L2 캐시를 보호하는 기술로, NUMA에서는 이것이 "노드 간 친화도"로 확장 적용된다.
OOM (Out Of Memory) 스왑 장애NUMA의 '로컬 메모리 100% 우선' 정책 때문에, 남의 노드 메모리가 남아도는데도 디스크 스왑을 치는 골때리는 억울한 현상이다.
가상 NUMA (vNUMA)거대 가상 머신(VM)이 멍청하게 스케줄링하지 않도록 하이퍼바이저가 아래쪽의 물리적 NUMA 지형도를 가상 머신에게 투명하게 보여주는 기술이다.
태스크셋 (Taskset) / Numactl관리자가 OS 커널 스케줄러의 멍청한 판단(오지랖)을 믿지 않고, 수동으로 특정 코어와 메모리 노드에 프로세스를 강제 결박하는 리눅스 실무 도구다.

👶 어린이를 위한 3줄 비유 설명

  1. 학교 급식실(메모리)이 1개일 땐 전교생 100명(코어)이 몰려가서 문 앞에서 매일 깔려 죽는 사고(병목)가 났어요.
  2. 그래서 교장 선생님이 NUMA라는 규칙을 만들어, 1반 교실 옆에 1반 전용 밥통, 2반 옆에 2반 전용 밥통을 따로 놔주었어요.
  3. 이제 아이들은 멀리 갈 필요 없이 자기 교실 앞 밥통(로컬 메모리)에서 1초 만에 초고속으로 밥을 먹을 수 있게 되어 학교가 평화로워졌답니다!