8. 질문 — NUMA와 캐시 일관성(MESI)
난이도 상내 답안
모범답안
모범답안 — NUMA와 캐시 일관성(MESI)
난이도: 상
핵심 답변
NUMA는 멀티 소켓 시스템에서 각 CPU가 자기 로컬 메모리는 빠르게, 다른 소켓의 메모리는 인터커넥트를 거쳐 느리게 접근하는 구조다. 스레드가 다른 노드의 메모리를 자주 건드리면 느려진다. MESI는 코어들의 캐시를 일관되게 유지하는 프로토콜로, 한 코어가 공유 데이터를 쓰면 다른 코어의 캐시 사본을 무효화(Invalidate)한다. 이 무효화 트래픽이 공유 쓰기가 많을수록 폭증해 확장성을 깎는다. 그래서 데이터·스레드를 같은 노드에 두고(affinity), 공유 쓰기를 줄이는(코어별 분산) 것이 핵심.
깊이 있는 설명
NUMA
- 소켓마다 자기 메모리 컨트롤러가 있어 로컬 접근은 빠르고(낮은 지연/높은 대역폭), 원격 노드 접근은 QPI/UPI 같은 인터커넥트를 거쳐 느리다.
- 흔한 함정: 메모리를 한 스레드(노드 0)에서 전부 할당해놓고 다른 노드 스레드들이 접근 → 전부 원격 접근. "first-touch" 정책상 처음 쓰는 스레드의 노드에 페이지가 매핑되므로, 할당 직후 해당 스레드가 초기화하게 만들어 로컬화한다.
MESI
- M(Modified): 이 캐시만 최신, 메모리는 낡음(쓰기 발생).
- E(Exclusive): 이 캐시만 갖고 있고 메모리와 동일.
- S(Shared): 여러 캐시가 같은 라인을 읽기 공유.
- I(Invalid): 무효(다른 코어가 써서 버려짐).
- 한 코어가 공유 라인에 쓰면 → 다른 코어 사본을 Invalidate하고 자기를 M으로. 다른 코어가 다시 읽으려면 그 라인을 가져와야(전송) 한다. 쓰기-쓰기 핑퐁이 잦으면 라인이 코어 사이를 계속 오가며(false sharing이면 더 심함) 성능 급락.
배치 전략
- 스레드를 코어에 고정(affinity), 그 스레드가 쓸 메모리는 같은 노드에 first-touch로 할당.
- 공유 카운터를 코어별로 분리(padding으로 캐시라인 분리) 후 주기적으로 합산 → 쓰기 경합·무효화 제거.
응용/실무 연결
- 대규모 서버: 존/샤드를 NUMA 노드에 매핑해 로컬리티 확보.
- 통계/메트릭: 스레드 로컬 누적 → 1초마다 머지(공유 카운터 경합 제거). 이것이 "코어별 카운터가 빠른 이유" — 매 증가마다 캐시라인을 빼앗기지 않기 때문.
흔한 오답·함정
- "코어만 많으면 빠르다" — NUMA 원격 접근·무효화 트래픽으로 오히려 느려질 수 있다.
- 큰 버퍼를 메인 스레드가 할당·초기화 후 워커가 사용 → 전부 원격 접근. first-touch 고려 필요.
- atomic 카운터 하나를 모든 코어가 증가 → 캐시라인 핑퐁으로 직렬화된 것처럼 느림.
꼬리질문 대비
- Q. false sharing과 MESI의 관계? false sharing은 논리적으로 다른 변수가 같은 라인에 있어, MESI 무효화가 불필요하게 발생하는 현상.
- Q. NUMA를 무시하면 항상 느린가? 작은 데이터/낮은 코어 수면 영향이 작다. 대규모·고경합에서 두드러진다.
- Q. 어떻게 측정하나?
numactl --hardware,perf c2c(캐시라인 경합), 노드별 메모리 대역폭 모니터링.