Skip to main content

NUMA, 메모리 관리의 새로운 세계

아래 내용은 DevOps와 SE를 위한 리눅스 커널 이야기 6장 NUMA, 메모리 관리의 새로운 세계를 읽고 정리한 내용입니다.

NUMA (Non-Uniform Memory Access)

UMA

devops-se-ch-6-1

NUMA

devops-se-ch-6-2

  • 멀티 프로세서 환경에서 적용되는 메모리 접근 방식
  • UMA(Uniform Memory Access)에서는 모든 프로세서가 공용 BUS를 이용해서 메모리에 접근하므로 BUS를 동시에 사용할 수 없으며 0번 소켓에 있는 CPU가 메모리에 접근하는 동안 1번 소켓에 있는 CPU는 메모리에 접근할 수가 없음
  • NUMA는 0번 CPU가 자신의 로컬 메모리에 접근하는 동안 1번 CPU도 자신의 로컬 메모리에 접근하여 성능 향상
  • 하지만 로컬 메모리의 양이 모자라 다른 CPU에 붙어있는 로컬 메모리에 접근하게 되고 이때 메모리 접근에 시간이 소요되어 예상치 못한 성능 저하 발생
  • 따라서 로컬 메모리에서 얼마나 많이 메모리 접근이 일어나느냐가 성능 향상의 가장 중요한 포인트

NUMA 메모리 할당 정책

[ec2-user@ip-172-31-35-50 ~]$ numactl --show
policy: default # 현재 적용된 정책
preferred node: current
physcpubind: 0
cpubind: 0
nodebind: 0
membind: 0

NUMA와 관련된 메모리 할당 정책은 총 4가지

  • default: 현재 프로세스가 실행되고 있는 프로세서가 포함된 노드에서 먼저 메모리를 할당 받아 사용
  • bind: 특정 프로세스를 특정 노드에 바인딩시키는 방식
  • preferred: bind가 반드시 설정한 노드에서 메모리를 할당 받는 반면, preferred는 가능한 한 설정한 노드로부터 메모리를 할당
  • interleaved: 다수의 노드에서 거의 동일한 비율로 메모리를 할당 받음
[ec2-user@ip-172-31-35-50 ~]$ numactl -H
available: 1 nodes (0)
node 0 cpus: 0 # cpu 번호
node 0 size: 964 MB # 메모리 크기
node 0 free: 406 MB
node distances:
node 0
0: 10

# 노드가 하나이지만 노드가 두 개일 경우 예시
# 리모트 메모리에 접근하는 시간이 로컬 메모리에 접근하는 데 필요한 시간의 2배
node 0 1
0: 10 20
1: 20 10
현재 시스템에 할당된 메모리의 상태 확인
numastat -cm
  • 전체 메모리에 free 영역이 많이 있는데도 불구하고 메모리 할당 정책에 따라 한쪽 노드에서 메모리 할당이 과하게 일어나면 swap을 사용하게 되는데 이럴 때 numastat을 활용하여 확인 가능
  • /proc/<pid>/numa_maps에 현재 동작 중인 프로세스의 메모리 할당 정책과 관련된 정보 기록

numactl로 할 수 있는 bind 정책

  • membind: 다른 소켓에 있는 CPU로 변경해도 메모리는 기존에 사용하던 노드에서 할당 받고 노드에서 사용 가능한 메모리의 영역 이상의 요청이 올 경우 swap 영역 사용, 메모리의 지역성을 살리지 못하고 신경 써야 할 부분이 많아 사용 미권장
  • cpunodebind: BIND 중에서도 특정 노드에 있는 CPU에서만 프로세스가 돌아가도록 설정, 메모리 할당도 해당 프로세스가 돌고 있는 CPU가 속해 있는 노드에서 할당 받기 때문에 메모리 지역성이 좋아진다.
  • physcpubind: cpunodebind가 특정 노드에 위치한 CPU를 프로세스에 매핑하는 개념이라면 physcpubind는 CPU 번호를 매핑
  • preferred: BIND와 비슷하지만 가능한 한 특정 노드에서 메모리를 할당받도록 하는 정책

각 노드에는 CPU와 메모리가 한 세트로 할당되어 있으며 성능상 같은 노드에 위치한 메모리 접근하는 것이 가장 좋다. 이를 메모리의 지역성을 높인다고 표현합니다.

numad

numactl을 통해 수동으로 NUMA 아키텍처에서의 메모리 할당 정책을 설정한다면 numad는 백그라운드 데몬과 같은 형태로 시스템에 상주하면서 프로세스들의 메모리 할당 과정을 주기적으로 살펴보고, 프로세스들을 최적화하는 작업을 담당합니다.

  • 다수의 프로세스가 존재할 때 local access와 remote access가 혼재하게 되는데 numad를 적용하여 하나의 프로세스가 필요로 하는 메모리를 하나의 노드에서만 할당 받을 수 있도록 설정할 수 있기 때문에 메모리의 지역성을 높이고 성능을 최적화할 수 있습니다.
  • 하지만 지역성을 너무 높인 탓에 메모리 불균형이 발생할 수밖에 없는 단점이 존재
  • 따라서 시스템 워크로드에 따라 numad를 켜는 것이 더 좋을지 잘 판단해서 적용

vm.zone_reclaim_mode

  • nomad 말고도 NUMA 아키텍처에서 메모리 할당에 영향을 주는 파라미터가 바로 vm.zone_reclaim_mode
[ec2-user@ip-172-31-47-71 ~]$ cat /proc/buddyinfo
Node 0, zone DMA 0 0 0 1 2 1 1 1 0 1 3
Node 0, zone DMA32 452 581 1400 599 288 40 26 27 15 15 453
Node 0, zone Normal 34 35 20 68 11 2 0 0 0 0 0

Linux의 메모리 관리 시스템은 물리 메모리를 여러 "zone"으로 나누어서 관리하며 위에서 DMA, DMA32, Normal와 같이 3개의 영역이 있는데 각 존마다 사용하는 영역이 다릅니다. DMA는 Direct Memory Access의 약자로 주로 오래된 하드웨어의 동작을 위해 존재하는 영역이며 Normal은 일반적인 용도로 사용되는 영역으로 커널, 프로세스 등이 메모리를 필요로 할 때 Normal 영역에서 메모리를 할당 받아서 사용합니다.

DMA (Direct Memory Access)

  • 이 영역은 0MB부터 시작하며 일반적으로 16MB까지의 메모리를 포함합니다.
  • 과거의 기기들(ISA 버스)은 16MB 이하의 주소로만 DMA 작업을 수행할 수 있었으며 이들 기기를 위해 영역이 유지되었습니다.

DMA32

  • 32비트 시스템에서 사용되는 모든 메모리와 64비트 시스템에서 처음 4GB의 메모리를 포함하는 영역
  • 이 영역은 일부 64비트 아키텍처에서 32비트 DMA 기기들을 지원하기 위해 필요

Normal

  • DMA 및 DMA32 영역을 제외한 나머지 메모리 영역
  • 대부분의 시스템에서는 이 영역이 메인 메모리의 주된 부분을 차지

  • vm.zone_reclaim_mode는 위와 같이 3개 영역들 사이에서 특정 영역의 메모리가 부족할 경우 다른 영역의 메모리를 할당할 수 있게 해주는 파라미터
  • vm.zone_reclaim_mode 파라미터는 총 4개의 값을 가질 수 있지만 실제적으로 중요한 값은 0과 1입니다.
  • 0은 disable을 의미하며 zone 안에서 재할당 ❌
    • 다른 zone에서 가져와서 사용
  • 1은 enable을 의미하며 zone 안에서 재할당 ⭕️

vm.zone_reclaim_mode 파라미터 값이 0이 되면 page cache 등과 같은 재할당 대상 메모리들이 반환되지 않고 다른 노드에 있는 메모리를 할당 받아서 사용합니다. 파일 서버와 같이 다수의 I/O가 발생하는 서버의 경우 메모리에 대한 로컬 액세스를 통해 얻을 수 있는 이점보다 많은 양의 page cache를 확보함으로써 얻을 수 있는 이점이 더 크기 때문에 vm.zone_reclaim_mode를 0으로 설정해서 사용하는 것이 더 좋으며 반대로 page cache 확보보다는 메모리에 대한 로컬 액세스 방식이 성능상 더 유리할 때는 vm.zone_reclaim_mode를 1로 설정해서 가능한 한 동일한 노드에서 메모리를 할당받을 수 있게 해주는 것이 좋다고 합니다.

I/O 집중적인 워크로드에서는 디스크에서 읽기/쓰기 연산이 빈번하게 일어나며 디스크 연산은 RAM 연산에 비해 상당히 느립니다. 그래서 리눅스와 다른 운영체제들은 디스크에서 읽은 데이터를 RAM에 임시로 저장하는 기능을 가지고 있는데 이를 'page cache'라고 하며 페이지 캐시의 주요 목적은 다음과 같습니다.

  • 자주 접근되는 데이터는 디스크에서 다시 읽지 않고 빠르게 RAM에서 가져오도록 하여 성능 향상
  • 쓰기 연산을 RAM에서 빠르게 처리한 후, 나중에 일괄적으로 디스크에 쓰는 방식으로 디스크 I/O를 최적화

  • vm.zone_reclaim_mode = 1인 경우 메모리가 부족할 때, 현재 노드의 페이지 캐시를 해제하고 그 공간에 새로운 데이터를 저장
  • vm.zone_reclaim_mode = 0인 경우 메모리가 부족할 때, 현재 노드의 페이지 캐시를 보존하면서 다른 노드의 RAM을 활용하여 메모리를 할당 받아 페이지 캐시를 최대한 활용하면서도 필요한 메모리 공간을 확보

파일 서버와 같은 I/O가 빈번한 서버

  • page cache는 파일 시스템의 데이터를 메모리에 캐시하므로 자주 사용되는 페이지에 대한 디스크 액세스를 줄여 성능을 향상하므로 메모리 액세스의 로컬성보다는 더 많은 page cache를 확보하는 것이 중요

일부 애플리케이션

  • 원격 노드에 대한 메모리 액세스를 최소화하고 성능을 향상