대규모 베어메탈 환경에서 Dual-Socket 이상 Intel Xeon(Sapphire Rapids/Emerald Rapids 등) 프로세서를 기반으로 Cilium Native Routing, 고성능 분산 스토리지(MinIO AIStor), 그리고 연산 엔진(StarRocks, Spark)을 구동할 때 UPI(Ultra Path Interconnect) 버스 트래픽 경합과 크로스 소켓 메모리 접근(Remote Memory Access)은 P99 지연 시간과 최대 Throughput을 갉아먹는 주범입니다.
OS, K8s 플랫폼, 그리고 레이어별 워크로드(스토리지, 쿼리 엔진, CNI) 관점에서 NUMA 친화성(NUMA Locality)을 극대화하는 엔드투엔드 전략입니다.
K8s 상위 설정 이전에 하드웨어 및 커널 레벨에서 NUMA 도메인을 노출하고 인터럽트 경로를 정렬해야 합니다.
numa_balancing=0: Linux 커널의 자동 NUMA 밸런싱(Background page scanning/migration)은 대규모 DB/스토리지 구동 시 무작위 레이턴시 스파이크를 유발하므로 끕니다. K8s 레벨에서 정적으로 바인딩하는 것이 유리합니다.transparent_hugepage=never (또는 madvise): MinIO, StarRocks, PostgreSQL 구동 시 메모리 압축(Compaction)으로 인한 락 경합 방지.irqbalance 비활성화 또는 격리):bond1(내부망 Intel E810)이 물리적으로 체결된 PCIe 슬롯의 NUMA 노드를 식별합니다.cat /sys/class/net/bond1/device/numa_node
# 또는 물리 슬롯 인터페이스 확인: cat /sys/class/net/<ethX>/device/numa_node
ice) 인터럽트 CPU 마스크를 해당 NUMA 노드의 코어로 강제 제한합니다.K8s 스케줄러와 Kubelet이 컨테이너에 CPU와 로컬 메모리를 동일 NUMA 도메인에서 단일 단위로 할당하도록 유도합니다.
Kubespray 인벤토리(group_vars/k8s_cluster/k8s-cluster.yml)에 다음 kubelet 플래그를 주입합니다.
# kubespray group_vars 설정
kubelet_custom_flags:
- "--cpu-manager-policy=static"
- "--cpu-manager-policy-options=full-pcpus-only=true"
- "--topology-manager-policy=single-numa-node"
- "--topology-manager-scope=container"
- "--reserved-cpus=0-3,64-67" # OS/Cilium/Kubelet 시스템 데몬 전용 격리 코어 (소켓 0/1 분할)
topology-manager-policy: single-numa-node:cpu-manager-policy: static + `Guaranteed QoS`:limits.cpu == requests.cpu (정수 단위) 및 limits.memory == requests.memory를 설정하면 Kubelet이 cgroups cpuset.cpus 및 cpuset.mems를 로컬 NUMA 도메인에 완전히 하드 바인딩합니다.[NUMA Node 0 (Socket 0)] [NUMA Node 1 (Socket 1)]
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ NIC (E810 bond1) │ │ NVMe Controller Pool B │
│ Cilium eBPF Routing Stack │ │ │
│ MinIO Server Pod 1 │ │ StarRocks BE Pod 1 │
│ (Direct NVMe Pool A) │ │ (In-Memory Query Compute) │
└──────────────────────────────┘ └──────────────────────────────┘
▲ ▲
└───────── UPI Link (경합 최소화) ───────┘
MinIO는 NVMe I/O와 E810 NIC 네트워크 I/O의 처리량이 균형을 이뤄야 합니다. NIC와 드라이브 버스가 연결된 NUMA 도메인에 파드를 바인딩해야 합니다.
resources:
limits:
cpu: "16" # 소켓 단일 NUMA 노드 내 코어 수 이하 정수
memory: "64Gi"
requests:
cpu: "16"
memory: "64Gi"
GOMAXPROCS=16 명시 (cpuset 경계를 넘어 Go 런타임이 다른 소켓 코어로 고루틴을 훔쳐가는(Work-stealing) 오버헤드 방지).GOGC=100, GOMEMLIMIT=58GiB 설정으로 불필요한 크로스 NUMA 메모리 스왑 방지.StarRocks Backend(BE)는 대규모 분산 Hash Join 및 집계 시 L3 캐시 및 로컬 메모리 대역폭(Memory Bandwidth)이 쿼리 병목의 80% 이상을 차지합니다.
resources.requests/limits를 소켓 1개의 여유 코어 수(예: 32코어/128GiB)로 정확히 분할.be.conf 최적화:# 백엔드 코어 수에 맞춘 파이프라인 엔진 스레드 격리
pipeline_exec_thread_pool_size = 32
# 원격 메모리 할당 회피
chunk_reserved_bytes_limit_enable = true
MALLOC_CONF="dirty_decay_ms:2000,muzzy_decay_ms:2000,narenas:32"Spark 워크로드는 드라이버와 익스큐터 간 Shuffle 단계에서 대량의 메모리 직렬화/역직렬화 및 디스크 I/O가 발생합니다.
# 익스큐터당 코어 및 메모리를 단일 NUMA 노드 크기로 제약
spark.executor.cores=8
spark.executor.memory=28g
spark.executor.memoryOverhead=4g
# K8s Guaranteed QoS 트리거를 위한 명시적 오버헤드 반영
spark.kubernetes.executor.request.cores=8
spark.kubernetes.executor.limit.cores=8
# Off-heap 메모리 할당 시 크로스 소켓 바인딩 억제
spark.memory.offHeap.enabled=true
spark.memory.offHeap.size=4g
OLTP 성격의 마스터/레플리카 DB는 Commit 트랜잭션 지연 시간과 Buffer Pool 히트가 핵심입니다.
single-numa-node 정책 하에 단일 소켓에 완전 격리.resources:
limits:
cpu: "8"
memory: "32Gi"
hugepages-2Mi: "16Gi"
requests:
cpu: "8"
memory: "32Gi"
hugepages-2Mi: "16Gi"
postgresql.conf):shared_buffers = '14GB' (할당된 로컬 HugePages 대역과 일치)huge_pages = onCilium의 eBPF 맵(BPF Maps)과 XDP/tc 훅은 커널 공간 메모리를 공유하므로, 네트워크 패킷이 들어오는 NIC의 로컬 CPU에서 처리되지 않으면 패킷마다 Inter-Socket 인터럽트가 발생합니다.
daemonset) Daemon CPU 바인딩:--reserved-cpus 영역 중, E810 NIC가 위치한 NUMA 소켓의 시스템 코어에 Cilium Agent와 BGP Control Plane이 스케줄링되도록 nodeAffinity 또는 데몬셋 설정을 최적화합니다.bpf:
# 맵 크기를 적절히 제한하여 과도한 메모리 분산 방지
mapDynamicSizeRatio: 0.005
preallocateMaps: true # 동적 맵 할당으로 인한 비로컬 메모리 참조 방지
routingMode: native
autoDirectNodeRoutes: true
loadBalancer:
mode: dsr # DSR(Direct Server Return) 적용 시 인그레스 트래픽의 불필요한 홉과 소켓 간 릴레이 방지
배포 후 실제 Pod들이 NUMA 경계 내에 갇혀있는지 확인하는 실측 검증 명령어입니다.
# 특정 StarRocks 또는 MinIO 컨테이너의 PID 확인
CONTAINER_ID=$(crictl ps --name starrocks -q)
PID=$(crictl inspect $CONTAINER_ID | jq .info.pid)
# 바인딩된 CPU 코어와 메모리 노드 확인
cat /proc/$PID/status | grep -E "Cpus_allowed_list|Mems_allowed"
# 출력 예: Mems_allowed: 0 (반드시 0 또는 1처럼 단일 NUMA 노드만 찍혀야 정상)
# numa_miss 및 foreign 카운터가 급증하지 않는지 확인
numastat -c starrocks_be
# 또는 시스템 전체 검사
watch -n 1 'numastat -m'
# Intel Uncore 카운터를 통해 UPI 대역폭 모니터링 (Gen4/Gen5 Xeon)
perf stat -a -e uncore_upi_0/event=0x01,umask=0x01/ -I 2000