NUMA 환경에서 메모리 접근 최적화 방법

NUMA 환경에서 메모리 접근 최적화 방법

현대의 고성능 서버 시스템은 대부분 ‘NUMA(Non-Uniform Memory Access)’ 아키텍처를 기반으로 합니다. NUMA는 여러 개의 프로세서(CPU)와 각각의 로컬 메모리 뱅크가 서로 연결되어 있는 구조를 의미하며, 이는 시스템의 확장성을 높이는 동시에 복잡한 메모리 접근 패턴을 야기합니다. 이 아키텍처를 제대로 이해하고 메모리 접근을 최적화하는 것은 애플리케이션의 성능을 극대화하고 시스템 자원을 효율적으로 활용하는 데 필수적입니다.

NUMA란 무엇이며 왜 중요한가요

NUMA는 말 그대로 ‘균일하지 않은 메모리 접근’을 의미합니다. 기존의 UMA(Uniform Memory Access) 아키텍처에서는 모든 CPU가 동일한 속도로 시스템 메모리에 접근할 수 있었습니다. 하지만 프로세서 코어 수가 증가하고 메모리 용량이 커지면서, 모든 CPU가 하나의 공유 메모리 버스를 통해 메모리에 접근하는 방식은 병목 현상을 일으키기 시작했습니다.

NUMA는 이러한 문제를 해결하기 위해 등장했습니다. 각 CPU 소켓(또는 CPU 다이)은 자체적인 로컬 메모리 컨트롤러와 연결된 메모리 뱅크를 가집니다. 이를 ‘NUMA 노드’라고 부릅니다. 특정 CPU가 자신의 로컬 NUMA 노드에 있는 메모리에 접근할 때는 매우 빠르게 데이터를 가져올 수 있습니다. 하지만 다른 NUMA 노드에 있는 메모리에 접근해야 할 경우에는, 노드 간의 인터커넥트(예: Intel QPI, AMD Infinity Fabric)를 통해 데이터를 가져와야 하므로 더 많은 시간이 소요됩니다. 이처럼 메모리 접근 속도가 ‘균일하지 않다’는 것이 NUMA의 핵심 개념입니다.

NUMA 환경에서 메모리 접근을 최적화하는 것이 중요한 이유는 다음과 같습니다.

  • 성능 병목 현상 완화 원격 메모리 접근은 로컬 접근보다 지연 시간이 길고 대역폭이 낮을 수 있습니다. 이는 애플리케이션의 성능을 저하시키는 주요 원인이 됩니다.
  • 자원 효율성 증대 최적화를 통해 CPU가 메모리 대기 시간으로 낭비하는 시간을 줄이고, 실제 작업을 처리하는 데 더 많은 시간을 할애할 수 있습니다.
  • 확장성 확보 대규모 서버 환경에서 NUMA 최적화는 수십, 수백 개의 코어를 가진 시스템에서 애플리케이션이 효율적으로 작동하도록 돕습니다.

NUMA 환경에서의 메모리 접근 원리

NUMA 환경에서는 각 CPU 소켓이 하나의 NUMA 노드로 간주되며, 이 노드에 직접 연결된 메모리를 ‘로컬 메모리’라고 합니다. 다른 CPU 소켓에 연결된 메모리는 ‘원격 메모리’가 됩니다. CPU가 데이터를 요청할 때, 운영체제는 기본적으로 해당 CPU와 동일한 NUMA 노드의 로컬 메모리에 데이터를 할당하려고 시도합니다. 이를 ‘첫 번째 터치(First Touch)’ 정책이라고도 합니다. 즉, 어떤 CPU가 처음으로 특정 메모리 페이지에 접근했는지에 따라 해당 페이지가 어느 NUMA 노드에 할당될지 결정될 수 있습니다.

문제는 애플리케이션이 여러 스레드나 프로세스에 걸쳐 데이터를 공유하거나, 특정 스레드가 원격 메모리에 자주 접근해야 하는 경우 발생합니다. 이 경우 원격 메모리 접근으로 인한 지연 시간이 누적되어 전체 시스템 성능에 악영향을 미칠 수 있습니다.

실용적인 NUMA 메모리 접근 최적화 전략

NUMA 환경에서 메모리 접근을 최적화하기 위한 몇 가지 실용적인 방법은 다음과 같습니다.

1. 프로세스 및 스레드 어피니티 설정

가장 기본적인 최적화 방법은 특정 프로세스나 스레드를 특정 NUMA 노드의 CPU 코어에 ‘고정’시키는 것입니다. 이렇게 하면 해당 프로세스가 주로 사용하는 메모리가 해당 NUMA 노드의 로컬 메모리에 할당될 가능성이 높아집니다.

  • Linux의 numactl 명령어 활용

    numactl은 프로세스의 NUMA 정책을 제어하는 강력한 도구입니다. 예를 들어, 특정 프로세스를 NUMA 노드 0의 CPU에 바인딩하고, 메모리도 노드 0에서만 할당받도록 설정할 수 있습니다.

    numactl --cpunodebind=0 --membind=0 my_application

    위 명령어는 `my_application`을 노드 0의 CPU에서 실행하고, 메모리도 노드 0에 할당하도록 강제합니다. `–localalloc` 옵션을 사용하면 현재 실행 중인 CPU의 로컬 메모리에 할당하도록 시도합니다.

    numactl --localalloc my_application
  • taskset 명령어 활용taskset은 특정 프로세스를 CPU 코어에 바인딩하는 데 사용됩니다. NUMA 노드를 명시적으로 지정하지는 않지만, 특정 코어 세트에 프로세스를 고정하여 간접적으로 NUMA 최적화에 기여할 수 있습니다.
    taskset -c 0-3 my_application

    이 명령어는 `my_application`을 CPU 0번부터 3번 코어에서만 실행하도록 제한합니다. 이는 해당 코어들이 속한 NUMA 노드에 메모리가 할당될 확률을 높입니다.

2. 메모리 할당 정책 조정

운영체제가 메모리를 할당하는 방식을 조절하여 NUMA 효율성을 높일 수 있습니다.

  • 첫 번째 터치(First Touch) 정책 이해 및 활용대부분의 운영체제는 ‘첫 번째 터치’ 정책을 따릅니다. 즉, 어떤 CPU가 처음으로 특정 메모리 페이지에 쓰기 접근을 시도하면, 해당 페이지는 그 CPU의 로컬 NUMA 노드에 할당됩니다. 따라서 애플리케이션 초기화 단계에서 각 스레드가 자신이 사용할 데이터를 미리 초기화(쓰기 접근)하도록 설계하면, 데이터가 해당 스레드의 로컬 NUMA 노드에 할당되도록 유도할 수 있습니다.
  • 명시적인 메모리 할당 API 사용

    개발자는 libnuma 라이브러리(Linux)와 같은 NUMA 관련 API를 사용하여 메모리 할당을 더욱 세밀하게 제어할 수 있습니다. 예를 들어, numa_alloc_onnode() 함수를 사용하여 특정 NUMA 노드에 메모리를 할당할 수 있습니다. 이는 특정 데이터 구조가 특정 NUMA 노드에서만 사용될 때 매우 유용합니다.

3. 데이터 구조 설계 및 접근 패턴 개선

애플리케이션 코드 레벨에서의 최적화는 매우 중요합니다.

  • 데이터 지역성(Data Locality) 확보

    자주 함께 사용되는 데이터를 물리적으로 가깝게 배치하여 캐시 효율성을 높이고, 원격 NUMA 노드 접근을 최소화해야 합니다. 예를 들어, 배열이나 구조체를 설계할 때, 관련 데이터를 한 블록에 모아두는 것이 좋습니다.

  • NUMA 노드 간 데이터 공유 최소화

    여러 NUMA 노드의 스레드들이 동일한 데이터에 빈번하게 접근해야 하는 상황은 원격 메모리 접근을 유발하고 캐시 일관성(Cache Coherency) 오버헤드를 증가시킬 수 있습니다. 가능한 한 각 스레드나 프로세스가 자신의 NUMA 노드 내에서만 데이터를 처리하도록 애플리케이션을 재설계하는 것이 좋습니다.

  • ‘False Sharing’ 방지

    서로 다른 NUMA 노드의 스레드들이 서로 다른 데이터라도 같은 캐시 라인에 위치한 데이터를 수정할 때 ‘False Sharing’이 발생할 수 있습니다. 이는 캐시 일관성 프로토콜로 인해 불필요한 캐시 라인 무효화와 동기화를 유발하여 성능을 저하시킵니다. 데이터 구조를 패딩(padding)하거나 정렬(alignment)하여 각 스레드가 사용하는 데이터가 서로 다른 캐시 라인에 있도록 설계하면 False Sharing을 방지할 수 있습니다.

4. 가상화 환경에서의 NUMA 최적화

가상 머신(VM) 환경에서도 NUMA는 중요합니다. 하이퍼바이저(Hypervisor)는 물리적 NUMA 노드를 가상 머신에 ‘vNUMA’ 형태로 노출할 수 있습니다.

  • vNUMA 구성

    VM에 할당된 CPU와 메모리 자원이 물리적 NUMA 노드 경계를 넘지 않도록 구성하는 것이 중요합니다. 예를 들어, 16코어, 64GB RAM을 가진 VM을 생성할 때, 8코어, 32GB RAM을 가진 두 개의 물리적 NUMA 노드에 걸쳐 VM을 배포하기보다는, 하나의 물리적 NUMA 노드 내에서 자원을 할당받도록 구성하는 것이 좋습니다.

  • VM 및 애플리케이션 배치

    하이퍼바이저는 NUMA 스케줄링 기능을 제공하여 VM을 특정 물리적 NUMA 노드에 고정하거나, VM의 vNUMA 구성을 기반으로 최적의 물리적 NUMA 노드에 배치할 수 있습니다. VM 내에서 실행되는 애플리케이션도 위에서 설명한 NUMA 최적화 기법을 적용해야 합니다.

NUMA 환경에서의 흔한 오해와 사실 관계

  • 오해 NUMA는 HPC(고성능 컴퓨팅) 환경에서만 중요하다.

    사실 현대의 대부분의 멀티 소켓 서버는 NUMA 아키텍처를 사용합니다. 데이터베이스, 웹 서버, 가상화 플랫폼 등 다양한 일반적인 서버 애플리케이션에서도 NUMA 최적화는 성능에 큰 영향을 미칩니다. 특히 메모리 집약적인 워크로드에서는 필수적입니다.

  • 오해 운영체제가 모든 NUMA 관련 문제를 자동으로 처리해 준다.

    사실 운영체제는 기본적으로 NUMA를 인식하고 메모리 할당 및 스케줄링을 최적화하려고 노력합니다. 하지만 애플리케이션의 특정 메모리 접근 패턴이나 스레드 동작 방식까지 완벽하게 예측하고 최적화하기는 어렵습니다. 따라서 애플리케이션 레벨에서의 명시적인 최적화가 필요합니다.

  • 오해 더 많은 RAM을 추가하면 NUMA 문제가 해결된다.

    사실 단순히 RAM 용량을 늘리는 것만으로는 NUMA 문제를 해결할 수 없습니다. 오히려 메모리 용량이 늘어날수록 원격 메모리 접근의 가능성이 커지고, 잘못된 할당은 성능 저하를 더욱 심화시킬 수 있습니다. 중요한 것은 메모리 용량뿐만 아니라, 데이터가 CPU에 얼마나 가깝게 위치하는지입니다.

비용 효율적인 NUMA 활용 방법

NUMA 최적화는 새로운 하드웨어 구매 없이 기존 시스템의 성능을 향상시키는 효과적인 방법입니다. 이는 다음과 같은 방식으로 비용 효율성을 높일 수 있습니다.

  • 기존 하드웨어의 성능 극대화NUMA 최적화를 통해 현재 보유하고 있는 서버의 CPU와 메모리 자원을 최대한 활용할 수 있습니다. 이는 값비싼 CPU나 추가 RAM 구매 없이도 애플리케이션 처리량을 늘리고 지연 시간을 줄일 수 있다는 의미입니다.
  • 하드웨어 업그레이드 주기 연장

    최적화된 시스템은 더 오랫동안 최적의 성능을 유지할 수 있으므로, 하드웨어 업그레이드 필요성을 늦추고 초기 투자 비용의 회수 기간을 늘릴 수 있습니다.

  • 에너지 효율 증대

    성능이 향상되면 동일한 작업을 더 짧은 시간에 완료하거나, 더 적은 자원으로 동일한 작업을 처리할 수 있습니다. 이는 서버의 에너지 소비를 줄여 운영 비용을 절감하는 효과로 이어질 수 있습니다.

전문가의 조언

NUMA 최적화는 단순한 설정 변경을 넘어 시스템과 애플리케이션에 대한 깊은 이해를 요구합니다. 전문가들은 다음과 같은 조언을 합니다.

  • ‘측정 없이는 최적화도 없다’

    어떤 부분이 병목인지 정확히 파악하는 것이 중요합니다. numastat, perf, top 등의 도구를 사용하여 시스템의 NUMA 통계, CPU 사용률, 메모리 접근 패턴 등을 면밀히 모니터링해야 합니다. 문제 영역을 식별한 후에야 효과적인 최적화 전략을 수립할 수 있습니다.

  • 점진적인 접근 방식

    모든 것을 한 번에 바꾸려 하지 말고, 가장 큰 영향을 미칠 것으로 예상되는 부분부터 작은 변경을 시도하고 그 결과를 측정하는 점진적인 접근 방식이 효과적입니다. 예를 들어, 먼저 numactl로 프로세스 어피니티를 설정해보고, 그 다음 애플리케이션 코드 레벨의 최적화를 고려하는 식입니다.

  • 워크로드 특성 이해

    애플리케이션이 메모리를 어떻게 사용하는지(읽기 위주인지, 쓰기 위주인지, 데이터 공유가 많은지 등)를 이해하는 것이 중요합니다. 워크로드의 특성에 따라 최적의 NUMA 전략이 달라질 수 있습니다.

자주 묻는 질문

1. 제 시스템이 NUMA 아키텍처를 사용하는지 어떻게 알 수 있나요

Linux 시스템에서는 lscpu 명령어를 실행하여 NUMA 관련 정보를 확인할 수 있습니다. 출력 결과에 ‘NUMA node(s):’ 항목이 있다면 해당 시스템은 NUMA 아키텍처를 사용하고 있는 것입니다. 또한 numactl --hardware 명령어를 통해 각 NUMA 노드의 CPU 및 메모리 정보를 상세하게 확인할 수 있습니다.

2. NUMA 최적화는 모든 애플리케이션에 필요한가요

모든 애플리케이션에 필수적인 것은 아닙니다. 단일 스레드 애플리케이션이나 메모리 사용량이 적은 애플리케이션의 경우 NUMA의 영향이 미미할 수 있습니다. 하지만 멀티 스레드, 멀티 프로세스 애플리케이션, 대규모 데이터 처리, 데이터베이스, 가상화 환경 등 메모리 대역폭과 지연 시간에 민감한 워크로드에서는 NUMA 최적화가 성능에 결정적인 영향을 미칩니다.

3. 개발자가 NUMA를 고려하여 코드를 작성해야 하나요

네, 가능하면 고려하는 것이 좋습니다. 특히 고성능이 요구되는 시스템이나 라이브러리를 개발할 때는 NUMA를 인지하고 데이터 구조 설계, 메모리 할당, 스레드 스케줄링 등을 최적화하는 것이 중요합니다. libnuma와 같은 API를 사용하여 명시적으로 NUMA 친화적인 코드를 작성할 수 있습니다.

4. NUMA 최적화가 가상 머신(VM)에도 적용되나요

네, 가상 머신 환경에서도 NUMA 최적화는 매우 중요합니다. 하이퍼바이저는 물리적 NUMA 노드를 VM에 vNUMA 형태로 노출할 수 있으며, VM의 CPU와 메모리 자원을 물리적 NUMA 노드 경계에 맞춰 할당하는 것이 성능에 큰 영향을 미칩니다. VM 내에서 실행되는 게스트 운영체제와 애플리케이션도 위에서 설명한 NUMA 최적화 기법을 적용할 수 있습니다.

댓글 남기기

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.