OpenShift 4.18 환경에서 오픈소스 HAProxy를 기반으로 사용자 정의 컨테이너 이미지를 구성하여
CPU 최적화 및 성능 향상을 실현한 사례를 정리한 내용이다.
HAProxy를 순수 TCP Pass-through 모드로 사용하며, 데이터 패킷 크기를 최신 CPU의 L1 Cache 사이즈에 맞춤으로써
Memory Access Latency와 Context Switching을 Zero에 수렴하게 만드는 전략이다.
HAProxy의 주요 버퍼 설정 값들을 16KB(16,384 bytes) 수준으로 맞추면, L1 Cache 구조에 최적화되어 처리 성능이 크게 향상된다.
최신 서버 CPU(Intel Xeon, AMD EPYC 등)의 아키텍처 특성을 고려하여, HAProxy의 내부 버퍼를 16KB(16,384 bytes)로 표준화한다.
global
tune.bufsize 16384
tune.pipesize 16384
tune.recv_enough 16384
tune.maxrewrite 1024-
L1 캐시 최적 적중 유도
대부분의 최신 CPU는 L1 Data Cache로 코어당 32KB 내외를 제공하며, 일반적으로 64byte 캐시 라인 단위로 운영된다.
HAProxy의 단일 버퍼 크기를 16KB로 설정할 경우, 하나의 연산 단위가 다수의 연속된 캐시 라인에 정렬되어 적중률이 높아지며,
L2/L3 Cache나 메모리 접근(Cache Miss)을 최소화할 수 있다. -
지속적 스트림 전송에 유리
데이터가 L1 내에서 처리될 확률이 높아짐에 따라 CPU가 고속의 SRAM 기반 캐시만을 사용하게 되어,
처리 속도와 일관성이 대폭 향상된다. -
Zero Copy & Splice 최적화
Linux 커널의 Pipe Buffer Size를 동일하게 16KB로 설정하면,
splice() system call 수행 시 userspace와 kernel space 간의 불필요한 memory copy가 제거된다.
데이터 흐름이 완전히 kernel space 내에서 이루어지므로, 불필요한 Context Switching 없이 Zero Copy 방식으로 처리된다.
이는 특히 초당 수십만 RPS급 트래픽 처리에 효율적으로 처리가 가능하다.
위의 16KB 전략이 실제 운영 환경에서 완벽하게 동작하기 위한 인프라 및 아키텍처 구성이다.
HAProxy를 mode tcp로 설정하여 http header parsing 과정을 생략한다.
이는 16KB 버퍼 전체를 순수 Data Payload 전송에만 사용하게 하여, Header 크기로 인한 buffer overflow 리스크를 차단하기 위함이다.
send-proxy-v2를 통해 TCP 연결 수립 단계에서 Binary Header로 Client IP를 전달한다.
tune.maxrewrite 1024의 작은 값 설정으로 별도의 메모리 할당 없이 즉시 header 추가가 가능하다.
소프트웨어 튜닝의 효과를 하드웨어가 뒷받침하기 위해 OpenShift 레벨에서 다음 설정을 적용한다.
NIC(Network Interface Card)가 장착된 NUMA 노드의 CPU와 메모리만을 사용하도록
Pod를 배치(Topology Manager)하여, QPI(QuickPath Interconnect)/UPI(UltraPath Interconnect)의 병목을 제거한다.
이는, OpenShift의 KubeletConfig 설정에서 CPU Manager를 활성화 하고
cpuManagerPolicy: static, topologyManagerPolicy: best-effort로 적용한다.
best-effort를 사용하는 경우 Pod 배포시 Resource Request 및 Limit을 정수(integer)로 설정하게 되면
qosClass가 Guaranteed 조건으로 성립되어, NUMA 코어를 할당할 수 있다.
다만, NUMA를 할당할 수 없는 상황(정수가 아닌 리소스 또는 NUMA 할당 실패)경우,
기존 CPU 할당 방식처럼 Pod를 배포할 수 있도록 Hybrid 방식으로 사용할 수 있도록 best-effort를 사용한다.
만일, NUMA 전용 Worker 노드를 구성하는 경우 topologyManagerPolicy는 restricted 또는 single-numa-node 정책을 사용하면 된다.
Redhat Docs - OpenShift: Using CPU Manager and Topology Manager
apiVersion: machineconfiguration.openshift.io/v1
kind: KubeletConfig
metadata:
name: 99-enable-numa-worker
spec:
machineConfigPoolSelector:
matchLabels:
pools.operator.machineconfiguration.openshift.io/worker: ""
kubeletConfig:
# CPU Manager(NUMA)
cpuManagerPolicy: static
cpuManagerReconcilePeriod: 5s
topologyManagerPolicy: best-effortHAProxy의 CPU Pinning을 통해 NUMA를 할당 받고, CPU Policy와 nbthread, cpu-map을 사용하여 성능을 극대화 한다.
nbthread와 cpu-map으로 CPU 코어 수에 1:1로 맞추되, CPU의 0번을 제외하고 설정하여, CPU 경합을 최소화하는 전략을 사용한다.
즉, CPU 0번을 제외하는 가장 큰 이유는, 해당 코어가 시스템 운영 및 인터럽트 처리에 사용되는 핵심 영역이기 때문에,
이를 회피함으로써 성능 저하 요소(IRQ, 커널 스케줄링 등)를 제거하고, HAProxy thread의 독립성과 실시간 처리 성능을 확보하기 위함이다.
만약, VM으로 구성된 Router Node가 4core, 8GiB 메모리로 구성되어 있다면,
Resource Request/Limit은 3core, 4GiB 메모리로 고정하여, qosClass의 Guaranteed 조건을 갖추고 CPU 0번을 제외하여 구성하도록 한다.
(이 경우 OpenShift에서 사용되는 리소스가 사용되므로, 이를 제외한 나머지 사용 가능한 리소스로 설정하면 된다.)
### CPU Pinning ####
# https://www.haproxy.com/blog/multithreading-in-haproxy/
# Multi-thread mode
nbthread 2
# Multi-process mode
cpu-map auto:1/1-2 1-2
# Forces the CPU frequency governor to 'performance' mode.
# This prevents the processor from entering low-power states (C-states) or downclocking,
# ensuring the CPU operates at maximum frequency at all times.
# Critical for high-throughput environments to eliminate "wake-up latency" and minimize response time jitter.
# https://www.haproxy.com/blog/how-haproxy-takes-advantage-of-multi-core-cpus
# https://www.haproxy.com/documentation/haproxy-configuration-tutorials/performance/performance-tuning
cpu-policy performanceHAProxy의 주요 버퍼(tune.bufsize 등)를 16KB로 정렬하면, 최신 CPU의 L1 Data Cache(32KB)에 적절히 배치되어,
고속의 SRAM(Static Random-Access Memory) 기반 캐시 적중률을 극대화한다.
이로 인해 L2/L3/DRAM에 의한 캐시 미스가 대폭 감소하고, 처리 지연(latency)이 최소화되며, Throughput이 비약적으로 향상된다.