Study/Linux

[Linux]NUMA

seomj 2024. 4. 18. 20:00

NUMA 아키텍처가 무엇인지, 메모리 할당에 어떤 영향을 미치는지


NUMA 아키텍처

Non-Uniform Memory Access (불균형 메모리 접근)

멀티 프로세서 환경에서 적용되는 메모리 접근 방식

 

UMA(Uniform Memory Access): 초창기 아키텍처

NUMA와 반대되는 개념

모든 프로세서가 공용 BUS를 이용해서 메모리에 접근하기 때문에, 동시에 접근이 불가능하다.

 

NUMA는 로컬 메모리로의 접근이 동시에 이뤄질 수 있다.

하지만 로컬 메모리의 양이 모자라면 다른 CPU에 붙어있는 메모리에 접근이 필요하게 되고, 이때 메모리 접근에 시간이 소요되어 예상치 못한 성능 저하를 경험하게 된다. 그래서 로컬 메모리에서 얼마나 많이 메모리 접근이 일어나느냐가 성능 향상의 가장 중요한 포인트이다.

Local Access: 각각의 CPU마다 별도의 메모리가 있는데 이와 같이 메모리에 접근하는 방식

Node: CPU와 메모리를 합친 것

Remote Access: NUMA에서 자신의 메모리가 아닌 다른 노드에 있는 메모리에 접근하는 방식

 

리눅스에서의 NUMA 확인

numactl

NUMA와 관련된 정책을 확인하거나 설정할 때 사용

root@worker1:~# numactl --show
policy: default
preferred node: current
physcpubind: 0 1 
cpubind: 0 
nodebind: 0 
membind: 0 

policy: default로, 기본 정책으로 설정되어 있다.

  • default: 현재 사용 중인 프로세스가 포함된 노드에서 메모리를 먼저 가져다가 사용하는 방식이다.
  • bind: 특정 프로세스를 특정 노드에 바인딩시키는 방식이다.
  • preferred: 선호하는 노드를 설정하는 방식이다.
  • interleaved: 다수의 노드에서 거의 동일한 비율로 메모리를 할당받는다. (Round-Robin)

bind와 preferred는 비슷해 보이지만, bind는 반드시 설정한 노드에서 메모리를 할당받고, preferred는 가능한 한 설정한 노드로부터 메모리를 할당받는다.

 

-H 옵션

root@worker1:~# numactl -H
available: 1 nodes (0)
node 0 cpus: 0 1
node 0 size: 1928 MB
node 0 free: 125 MB
node distances:
node   0 
  0:  10 

available을 통해 노드의 개수를 알 수 있다. 여기서는 1개로 구성되어 있다.

cpus는 노드에 해당하는 CPU의 번호이며 size와 free는 각 노드에 할당된 메모리의 크기이다.

distances는 각 노드의 메모리에 접근하는 데 걸리는 시간을 의미한다. 여기서는 0번 노드에서 0번 노드에 있는 메모리에 접근할 때 소요되는 시간이 10임을 의미한다. 이는 상대적인 값이다.

node distances:
node   0   1
  0:  10  20
  1:  20  10

만약 node가 0과 1로 구성되어 있고 distances가 다음과 같다면, 0번 노드에서 1번 노드에 있는 메모리에 접근하거나 1번 노드에서 0번 노드에 있는 메모리로 접근할 때 소요되는 시간이 20이며, 이는 리모트 메모리에 접근하는 시간이 로컬 메모리에 접근하는 데 필요한 시간의 2배라는 뜻이다.

 

numastat

현재 시스템에 할당된 메모리의 상태를 확인할 때 사용

root@worker1:~# numastat -cm

Per-node system memory usage (in MBs):
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
Token Node not in hash table.
                 Node 0 Total
                 ------ -----
MemTotal           1928  1928
MemFree              73    73
MemUsed            1855  1855
Active              351   351
Inactive            957   957
Active(anon)          2     2
Inactive(anon)      643   643
Active(file)        349   349
Inactive(file)      314   314
Unevictable           0     0
Mlocked               0     0
Dirty                 2     2
Writeback             0     0
FilePages           666   666
...

-c: 표시된 정보 테이블을 수평으로 축소한다.

-m: 노드별로 시스템 전체 메모리 사용량 정보를 표시한다.

해당 명령어를 통해 NUMA 아키텍처에서 메모리 불균형 상태를 확인할 수 있다. 전체 메모리에는 free 영역이 많이 있는데도 불구하고 메모리 할당 정책에 따라 한쪽 노드에서 메모리 할당이 과하게 일어나면 swap을 사용하게 되며, 이런 상태를 numastat를 통해서 확인할 수 있다.

 

프로세스가 어떤 메모리 할당 정책으로 실행되었는지 확인하는 방법

/proc/<pid>/numa_maps에는 현재 동작 중인 프로세스의 메모리 할당 정책과 관련된 정보가 기록된다.

root@worker1:~# cat /proc/2262/numa_maps 
55ba13924000 default file=/usr/libexec/gsd-media-keys
55ba1392f000 default file=/usr/libexec/gsd-media-keys mapped=28 active=0 N0=28 kernelpagesize_kB=4
55ba1394c000 default file=/usr/libexec/gsd-media-keys mapped=13 active=0 N0=13 kernelpagesize_kB=4

위의 프로세스는 default 정책으로 실행된 것이다.

다른 정책으로 실행된 프로세스를 찾다 포기했다…

 

메모리 할당 정책별 특징

default

default로 설정하면 현재 프로세스가 동작 중인 CPU가 속한 노드에서 메모리를 할당받는다. 그 순간순간 프로세스가 동작하고 있는 CPU를 기준으로 할당받는다.

 

0번 노드에 있는 CPU에 할당되어 0번 노드로부터 메모리를 할당받는다. taskset 명령으로 1번 노드에 있는 CPU에 강제로 할당했다. (taskset -pc 1 <pid>) taskset 명령을 입력한 순간부터 1번 노드로부터 메모리를 할당받는다.

→ 프로세스가 동작 중인 CPU의 노드에 따라 메모리를 할당한다.

 

기존 0번 노드에서 돌 때 확보한 메모리는 로컬 메모리가 아닌 원격 메모리가 되고, 이후의 접근은 로컬 액세스가 아닌 리모트 액세스가 된다.

리눅스 스케줄러는 가능한 한 기존에 바인딩된 노드에 계속 바인딩되도록 하려는 경향이 있기 때문에 한쪽 노드에서 할당 가능한 메모리의 양을 넘지 않는 한 크게 문제가 되지 않는다.

 

bind

membind

이전에 했던 방식과 동일하게 taskset을 실행했다.

더보기
  • 0번 노드에 있는 CPU에 할당되어 0번 노드로부터 메모리를 할당받는다. taskset 명령으로 1번 노드에 있는 CPU에 강제로 할당했다. (taskset -pc 1 <pid>)

 

강제로 1번 노드의 CPU에 할당해도 메모리는 여전히 0번 노드에서 할당받는다.

만약 노드에서 사용 가능한 메모리 영역 이상의 요청이 들어오면 어떻게 될까?

→ 1번 노드로의 메모리 할당은 일어나지 않으며, swap 영역을 사용하다가 OOM(Out of Memory)로 죽게 된다.

 

cpunodebind

특정 노드에 있는 CPU에서만 프로세스가 돌아가도록 설정한다.

 

0번 노드에서 더이상 할당받을 메모리가 없어지면 1번 노드를 통해서 메모리를 할당받는다.

이는 메모리 지역성을 높일 수 있기 때문에 membind 정책보다 선호된다.

다만 멀티 스레드로 동작하는 경우 CPU를 절반밖에 사용할 수 없기 때문에 CPU 리소스가 낭비될 수 있다.

❓ 멀티 스레드로 동작하면 왜 CPU를 절반밖에 사용할 수 없는거지?

 

physcpubind

CPU 번호를 매핑하는 개념

한쪽 노드에 위치한 CPU 번호를 나열하면 cpunodebind와 같은 개념으로 동작하고,

서로 다른 노드에 위치한 CPU 번호를 나열하면 해당 CPU에서만 프로세스가 실행되도록 설정된다.

 

preferred

가능한 한 특정 노드에서 메모리를 할당받도록 하는 정책이다.

 

가능한 0번 노드에서 받는 것이지 무조건 0번 노드에서 받는 것이 아니다.

설정한 노드 이상의 메모리를 사용하게 되면 다른 노드로부터 메모리를 할당받는다. 그렇기 때문에 OOM이 발생하지 않는다.

 

interleaved

다수의 노드로부터 공평하게 메모리 할당을 받는 정책이다.

한 노드 이상의 메모리 할당이 필요한 경우에 사용한다.

 

어느 노드에 속한 CPU에서 돌아가고 있는지 상관없이 각 노드들로부터 순차적으로 메모리 할당을 받는다.

 

numad를 이용한 메모리 할당 관리

리눅스에서는 numad를 통해 메모리 지역성을 높일 수 있는 방법을 제공해준다.

numad는 백그라운드 데몬과 같은 형태로 시스템에 상주하면서 프로세스들의 메모리 할당 과정을 주기적으로 살펴보고, 프로세스들을 최적화하는 작업을 담당한다.

 

numad의 긍정적인 영향

local access와 remote access가 혼재되어 있던 상태에서 local access가 우세하도록 바꼈다.

프로세스가 필요로 하는 메모리의 크기가 노드 하나의 메모리 크기보다 작기 때문에 충분히 메모리 지역성을 높일 수 있다.

 

numad의 단점

프로세스 A는 interleaved 정책으로 실행된 상태이다. 하지만 이때 프로세스 B가 실행되고, 이 프로세스는 메모리 요청이 노드 하나의 크기보다 작아서 numad에 의해 한쪽 노드에 바인딩되고 해당 노드로부터 메모리를 할당 받는다. 프로세스 B가 지역성을 높이기 위해 노드1에서 메모리 할당을 너무 많이 받아서 더 이상 프로세스 A에 할당해 줄 메모리가 없을 때 문제가 된다.

 

vm.zone_reclaim_mode 커널 파라미터

커널은 메모리를 사용 용도에 따라 zone이라 부르는 영역으로 구분하여 관리한다.

root@worker1:~# cat /proc/buddyinfo 
Node 0, zone      DMA     23      4      4      2      0      1      1      2      0      1      1 
Node 0, zone    DMA32    980    511    442    238    173     53     17      5      4      5      1 

여기서는 Node0이 2개 영역(DMA, DMA31)로 구분되어 있다.

DMA(Direct Memory Access)는 주로 오래된 하드웨어의 동작을 위해 존재하는 영역이다.

책에는 추가로 Normal이 존재한다. Normal은 커널, 프로세스 등이 메모리를 필요로 할 때 해당 영역에서 메모리를 할당 받아서 사용한다.

 

메모리 영역은 용도별로 구분되어 있으며 메모리는 용도에 맞는 곳에서 할당 받아서 사용된다.

 

vm.zone_reclaim_mode는 이런 영역들 사이에서 특정 영역의 메모리가 부족할 경우 다른 영역의 메모리를 할당할 수 있게 해준다.

  • 0: disable, zone 안에서 재할당하지 않는다. 즉, 다른 zone에서 가져와서 사용한다.
  • 1: enable, zone 안에서 재할당을 한다. zone 안에서 재할당 할 수 있는 영역을 먼저 찾아서 필요한 만큼 재할당해서 사용한다.

0이 되면 page cache 등과 같은 재할당 대상 메모리들이 반환되지 않고 다른 노드에 있는 메모리를 할당받아서 사용한다.

다수의 I/O가 발생하는 서버의 경우 많은 양의 page cache를 확보함으로써 얻을 수 있는 이점이 크기 때문에 0으로 설정해서 사용하는 것이 좋다. 반대로 로컬 액세스 방식이 성능상 더 유리할 때는 1로 설정한다.

 

NUMA 아키텍처의 메모리 할당 정책과 워크로드

가장 먼저 생각해볼 것은 사용할 메모리의 크기와 프로세스의 스레드 개수이다.

NUMA 노드 한 개 이상의 메모리를 사용하게 되는지, 프로세스가 싱글 스레드로 동작하는지 등을 확인해봐야 한다.

아래 내용들이 정답은 아니다. 참고할 수 있는 의견 중 하나이며, 책의 내용을 참고했다.

 

메모리가 노드 하나의 크기를 넘지 않고 싱글 스레드로 동작하는 경우

  • 1소켓의 UMA 아키텍처를 사용하는 서버를 사용하는 것이 가장 적합하다.
  • NUMA 아키텍처를 사용해야 하는 구조라면 bind 정책으로 특정 CPU에 바인딩하는 것이 도움이 된다. 싱글 스레드이기 때문에 하나 이상의 CPU가 필요하지 않다. 특정 CPU에 바인딩시킴으로써 CPU cache를 최대로 사용할 수 있다.
  • vm.zone_reclaim_mode도 1로 켜두면 하나의 노드에 메모리 할당이 몰리기 때문에 메모리의 로컬 액세스가 늘어나 성능에 도움이 된다.

 

메모리가 노드 하나의 크기를 넘지 않고 멀티 스레드로 동작하는 경우

  • 메모리 할당을 한 곳에서만 이루어지게 할 수 있다. cpunodebind 정책을 통해서 여러 개의 코어에 프로세스를 바인딩시키고 해당 노드에서만 메모리를 할당 받아서 사용하게 하면 성능이 가장 좋다.
  • 개별 CPU Usage를 세심하게 살펴보면서 CPU 리소스가 부족하지는 않은지 모니터링을 해야 한다.
  • vm.zone_reclaim_mode는 1이 성능에 유리할 수 있다.
  • numad가 가장 효과적으로 동작할 수 있는 워크로드이다.

 

메모리가 노드 하나의 크기를 넘고 싱글 스레드로 동작하는 경우

  • 메모리의 지역성을 올릴 수 있는 방법을 사용해야 한다. 프로세스가 싱글 스레드로 동작하기 때문에 리모트 액세스가 발생할 수 밖에 없다. 리모트 액세스를 어떻게 최소화할 것인지가 핵심이다.
    ❓ 싱글 스레드로 동작하는 데 왜 리모트 액세스가 발생하지?
  • CPU Cache 사용을 최적화하기 위해 cpunodebind 정책을 사용하는 것이 가장 좋다.
  • vm.zone_reclaim_mode는 0으로 하여, 처음부터 다수의 노드로부터 메모리를 할당 받는 것이 좋다.

 

메모리가 노드 하나의 크기를 넘고 멀티 스레드로 동작하는 경우

  • 메모리가 노드 하나의 크기를 넘기 때문에 리모트 액세스가 발생할 것이고, 멀티 스레드라서 여러 개의 스레드가 여러 개의 CPU에서 동작하게 된다. 이 경우에는 interleave 모드가 최적의 성능을 낼 수 있다. 어떤 CPU에 어떤 스레드가 바인딩될지 모르기 때문에 메모리 할당을 여러 영역에 넓게 펼치는 것이 좋다.
  • vm.zone_reclaim_mode는 0이 좋다.

 

정리

  • NUMA는 Non-Uniform Memory Access의 약자이며 불균형 메모리 접근을 의미한다.
  • 각 노드에는 CPU와 메모리가 한 세트로 할당되어 있으며 성능상 같은 노드에 위치한 메모리에 접근하는 것이 가장 좋다. 이를 메모리의 지역성을 높인다고 표현한다.
  • numstat을 통해서 현재 프로세스의 메모리 할당이 노드별로 어떻게 되어 있는지를 확인할 수 있다.
  • numactl을 통해서 원하는 NUMA 정책에 맞게 프로세스를 실행시킬 수 있다.
  • numad를 통해서 자동으로 프로세스들의 메모리 할당을 최적화할 수 있다.
  • vm.zone_claim_mode는 특정 zone의 메모리가 부족할 경우 어떻게 동작하게 할지를 결정하는 커널 파라미터이다.
  • bind 정책은 특정 노드에서 메모리를 할당받도록 강제하는 정책이다.
  • preferred 정책은 특정 노드에서 메모리를 먼저 할당받도록 하는 정책이다.
  • interleave 정책은 여러 노드에서 균등하게 받도록 하는 정책이다.
  • NUMA 아키텍처와 관련된 워크로드는 필요로 하는 메모리의 크기와 프로세스의 스레드 방식에 가장 많은 영향을 받는다.NUMA 아키텍처가 무엇인지, 메모리 할당에 어떤 영향을 미치는지

 

 

 

<참고>

DevOps와 SE를 위한 리눅스 커널 이야기

numstat: https://access.redhat.com/documentation/ko-kr/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-tool_reference-numastat

https://velog.io/@enosoup/%EC%8B%A4%EB%AC%B4-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%9A%94%EC%95%BD

'Study > Linux' 카테고리의 다른 글

[Linux]TCP Keepalive  (0) 2024.04.24
[Linux]TIME_WAIT 소켓  (0) 2024.04.22
[Linux]swap  (0) 2024.04.14
[Linux]free 명령과 메모리  (0) 2024.04.10
[Linux]Load Average와 시스템 부하  (0) 2024.04.09