26S06d

QK·약 8시간 전

패킷이 물리 스위치를 거쳐 노드에 도달하고, 리눅스 커널과 CNI(eBPF/Cilium)를 통과해 최종 Pod 내 애플리케이션 소켓에 도달하기까지의 전체 데이터 플레인(Data Plane) 경로 및 구간별 병목 지점입니다.

대규모 트래픽 환경(Spark 셔플, StarRocks 쿼리, vLLM/학습 분산 통신)에서 병목은 특정 계층의 버퍼 고갈, 락 경합, 불필요한 컨텍스트 스위칭에서 발생합니다.


[ 물리 스위치 (ToR/Spine) ]
         │ (물리 링크: 25G/100G, MTU, PFC/ECN)
         ▼
[ 1. NIC 하드웨어 계층 ] ─── Ring Buffer / RSS / PCIe 대역폭
         │ (DMA 전송 & Hard IRQ)
         ▼
[ 2. 드라이버 & 커널 NAPI ] ─── softirq (ksoftirqd) / CPU 코어 경합
         │ (sk_buff 할당 & eBPF 훅)
         ▼
[ 3. CNI / 네트워크 서브시스템 ] ─── Cilium(tc/XDP eBPF) / Conntrack / 라우팅
         │ (가상 인터페이스 통과 or BPF Host Routing 우회)
         ▼
[ 4. 커널 소켓 계층 (TCP/IP) ] ─── rmem/wmem 버퍼 / Listen 백로그 / TCP 윈도우
         │ (시스템 콜: read/epoll & 유저 공간 복사)
         ▼
[ 5. Pod 애플리케이션 계층 ] ─── Event Loop / 런타임 스레드 풀 / GC Stop-the-world

1. 물리 네트워크 & ToR 스위치 구간

물리 케이블을 타고 패킷이 서버의 NIC 포트에 도달하기 직전의 구간입니다.

  • Incast 현상 및 스위치 공유 버퍼 고갈 (Buffer Overflow)
  • 병목 원인: 분산 환경(예: MapReduce/Shuffle, All-Reduce)에서 수십 대의 워커가 1대의 타깃 노드로 동시에 응답을 보낼 때 ToR(Top-of-Rack) 스위치의 특정 포트 큐가 순간적으로 넘쳐 패킷 드롭(Drop)이 발생합니다.
  • 현상: TCP 재전송 타이머(RTO) 동작으로 인한 급격한 대역폭 저하 및 레이턴시 튐(Latency Spike).
  • 주요 확인 지표: 스위치 포트의 Egress Drop Counter, ECN Marked Packets.
  • 점보 프레임 (MTU) 불일치로 인한 패킷 단편화 (Fragmentation)
  • 병목 원인: 스위치는 MTU 1500인데 노드는 9000이거나, 반대로 경로상 특정 L3 장비가 작은 MTU를 가질 때.
  • 현상: DF(Don't Fragment) 패킷 드롭(ICMP Fragmentation Needed 발생) 또는 커널에서 패킷 재조립 오버헤드로 인한 CPU 급증.

2. NIC 하드웨어 & PCIe 버스 구간

NIC 물리 포트의 PHY 칩이 전기/광 신호를 프레임으로 변환하여 호스트 메모리로 전송(DMA)하는 단계입니다.

  • Rx/Tx 링 버퍼(Ring Buffer) 오버플로우
  • 병목 원인: 패킷 인입 속도가 OS 커널이 링 버퍼에서 디스크립터를 꺼내가는 속도보다 빠를 때 발생합니다.
  • 현상: 하드웨어 레벨 패킷 드롭.
  • 확인/해결:
  • 확인: ethtool -S <ethX> | grep -E "rx_dropped|rx_missed_errors|rx_discards"
  • 해결: ethtool -G <ethX> rx 4096 tx 4096 (버퍼를 하드웨어 최대치로 확장).
  • RSS(Receive Side Scaling) 해시 불균형 및 단일 코어 쏠림
  • 병목 원인: 트래픽의 4-tuple(Src/Dst IP, Src/Dst Port)이 고정되어 특정 Rx 큐로만 패킷이 몰리는 경우.
  • 현상: 전체 CPU는 한산한데 특정 1개 코어만 100%를 치면서 패킷 드롭 발생.
  • 확인: cat /proc/interrupts | grep <ethX> (특정 큐 카운터만 급증하는지 점검).
  • PCIe 슬롯 대역폭 및 NUMA 노드 불일치
  • 병목 원인: NIC가 CPU Socket 1의 PCIe 버스에 꽂혀 있는데, 패킷을 처리하는 드라이버/앱 메모리가 Socket 0에 있을 때.
  • 현상: 모든 패킷 DMA가 UPI(소켓 간 버스)를 건너가며 대역폭 포화 및 지연 시간 2~3배 증가.

3. 드라이버 & 커널 인터럽트 (softirq) 구간

NIC가 호스트 RAM에 패킷을 쓰고 커널에 "가져가라"고 알리는 단계입니다.

  • Hard IRQ \rightarrow SoftIRQ(NAPI) 처리 지연
  • 병목 원인: 하드웨어 인터럽트 발생 후 커널의 net_rx_action(SoftIRQ)이 실행되는데, 초당 패킷 수(PPS)가 수백만 개에 달하면 CPU가 소프트 인터럽트 처리에 갇힙니다.
  • 현상: top에서 %si(softirq) 수치가 100%에 근접하며 패킷 처리가 멈춤.
  • 확인/해결:
  • 확인: cat /proc/net/softnet_stat의 첫 번째 열(처리 건수) 대비 세 번째 열(time_squeeze: 처리 시간 초과로 양보한 횟수) 증가 여부.
  • 해결: sysctl -w net.core.netdev_budget=600, net.core.netdev_max_backlog=100000.

4. CNI (Cilium eBPF) & 커널 네트워크 스택 구간

패킷이 파드 IP를 찾아 라우팅되고 필터링되는 논리적 네트워크 구간입니다.

  • Cilium BPF Host Routing vs veth 페어 오버헤드
  • 병목 원인: 표준 veth 방식은 패킷이 veth 인터페이스를 거치면서 TCP/IP 스택을 두 번 통과(노드 네임스페이스 \rightarrow 파드 네임스페이스)하고 불필요한 컨텍스트 스위칭이 발생합니다.
  • 해결: Cilium Native Routing 모드에서 eBPF 호스트 라우팅(Host Routing)을 활성화하면 tc(traffic control) 훅 레벨에서 커널 스택을 우회하여 곧바로 목적지 소켓/네임스페이스로 바이패스됩니다.
  • Conntrack(연결 추적) 테이블 고갈 및 Lock 경합
  • 병목 원인: 짧은 수명의 TCP 연결(Short-lived HTTP/REST)이 폭주할 때 nf_conntrack 테이블이 가득 참.
  • 현상: nf_conntrack: table full, dropping packet 커널 로그와 함께 신규 세션 드롭.
  • 확인: sysctl net.netfilter.nf_conntrack_count vs net.netfilter.nf_conntrack_max.
  • eBPF Map 동적 메모리 할당 및 락 경합
  • 병목 원인: 대규모 엔드포인트 환경에서 BPF Map 크기가 작거나 사전 할당(preallocateMaps)되지 않아 동적 할당 오버헤드 발생.

5. 커널 소켓 버퍼 (L4 TCP Layer) 구간

커널이 수신한 TCP 세그먼트를 순서대로 조립하여 애플리케이션의 소켓 수신 큐(Receive Queue)에 담는 구간입니다.

  • 소켓 버퍼(rmem/wmem) 크기 제한 및 TCP Zero Window
  • 병목 원인: BDP(Bandwidth-Delay Product, 대역폭 ×\times RTT)에 비해 net.ipv4.tcp_rmem이 너무 작으면 송신측에 ACK를 보낼 때 윈도우 크기를 0으로 줄여버립니다(TCP ZeroWindow).
  • 현상: 네트워크 선로는 100G인데 실제 전송 속도는 수십 Mbps로 급락.
  • 확인: ss -nt 명령에서 Recv-Q가 가득 차 있는지 확인.
  • 소켓 Listen Backlog 오버플로우 (somaxconn)
  • 병목 원인: 신규 TCP 3-Way Handshake가 폭증할 때 커널의 대기 큐(SYN Backlog / Accept Queue) 크기가 작으면 SYN 패킷을 버립니다.
  • 현상: 클라이언트 측에서 간헐적인 Connection refused 또는 Connection timed out.
  • 확인: netstat -s | grep -i "listen" (times the listen queue of a socket overflowed).

6. Pod 내 애플리케이션 계층 (User Space)

커널 메모리 공간(Kernel Space)의 소켓 버퍼에서 사용자 프로세스(User Space)로 데이터가 넘어와 실제 비즈니스 로직에 들어가는 구간입니다.

  • 시스템 콜(read/recv) 오버헤드 및 커널-유저 메모리 복사
  • 병목 원인: 애플리케이션이 버퍼를 작게 잡아 read() 시스템 콜을 지나치게 자주 호출하면 CPU가 유저-커널 모드 전환(Context Switch)에 낭비됩니다.
  • I/O 멀티플렉싱(Event Loop) 지연 및 스레드 풀 블로킹
  • 병목 원인: Netty, epoll 기반 비동기 프레임워크(vLLM의 Uvicorn, Spark RPC 등)에서 I/O 스레드가 무거운 연산(동기식 직렬화/역직렬화)을 처리하느라 이벤트 루프가 멈추는 현상.
  • 현상: 커널의 소켓 Recv-Q는 차오르는데 애플리케이션이 이를 꺼내가지 못해 타임아웃 발생.
  • 런타임 GC Pause (Stop-The-World)
  • 병목 원인: Java(Spark)나 Go(MinIO) 환경에서 힙 메모리가 수십~수백 GB일 때 GC가 돌며 전체 프로세스 스레드가 일시 중지됨.
  • 현상: 하위 계층(NIC~커널)은 정상인데 수 초간 네트워크 패킷 처리가 중단되어 TCP 연결이 끊어짐.

구간별 진단 및 병목 포인트 매핑

구간주요 증상핵심 진단 명령 / 메트릭
스위치/물리패킷 손실, RTO 재전송 증가ToR CLI, `netstat -s
NIC / Ring프레임 폐기, Rx 에러`ethtool -S
인터럽트 (NAPI)CPU %si 100% 포화mpstat -P ALL 1, /proc/net/softnet_stat
CNI (Cilium)Conntrack 풀, BPF 드롭cilium bpf drop list, `dmesg
커널 TCP 소켓ZeroWindow, 수신 큐 정체ss -itmp, `netstat -s
애플리케이션응답 지연, EventLoop 지연pidstat -w, JVM GC 로그, pprof

===

CPU 병목은 단순히 top 명령어에서 CPU 사용률이 100%를 찍는 현상만을 의미하지 않습니다. 최신 고성능 멀티소켓 서버(Intel Xeon 등)에서는 CPU 코어의 파이프라인 연산기 자체는 놀고 있는데(Stall), 메모리나 버스를 기다리느라 멈춰 있는 대기 병목이 전체 성능 저하의 절반 이상을 차지합니다.

실제 마이크로아키텍처 파이프라인부터 메모리 계층, OS 스케줄러, 컨테이너 Cgroups 제약까지 하부 하드웨어에서 상위 애플리케이션에 이르는 구간별 CPU 병목 지점을 정리했습니다.


[ 1. CPU 코어 & 파이프라인 ] ─── 프론트엔드 분기 예측 실패 / 백엔드 실행 유닛 포화
         │ 
[ 2. 캐시 계층 (L1/L2/L3) ] ─── 캐시 라인 미스 / False Sharing (캐시 바운싱)
         │ 
[ 3. 메인 메모리 & 인터커넥트 ] ─── UPI 버스 경합 / 원격 NUMA 접근 지연 / 메모리 대역폭 포화
         │ 
[ 4. 커널 & 스케줄러 계층 ] ─── 컨텍스트 스위칭 오버헤드 / 락(Spinlock) 경합 / 시스템 콜
         │ 
[ 5. 컨테이너/K8s Cgroups ] ─── CFS 쿼터에 의한 CPU Throttling / 코어 분산 배치

1. 마이크로아키텍처 파이프라인 구간 (Core Execution Level)

CPU 코어 내부에서 기계어 명령어를 디코딩하고 연산 유닛(ALU, FMA 등)으로 밀어 넣는 초미세 연산 구간입니다. 인텔 Top-down 분석 방법론(TMA)의 핵심 영역입니다.

  • 프론트엔드 바운드 (Front-End Bound) & 분기 예측 실패 (Branch Misprediction)
  • 원인: 복잡한 조건 분기문(if/else)이 많은 코드나 거대한 바이너리에서 CPU 분기 예측기(Branch Predictor)가 다음 실행 경로를 틀리게 예측할 때 발생합니다.
  • 현상: 기껏 파이프라인에 채워 넣은 명령어들을 전부 폐기(Flush)하고 처음부터 다시 디코딩하느라 수십 사이클 동안 CPU가 유휴(Stall) 상태가 됩니다.
  • 확인: perf stat -e branch-misses,branches ./app
  • 백엔드 코어 바운드 (Back-End Core Bound)
  • 원인: SIMD/벡터 연산(AVX-512, AMX 등)이나 부동소수점 곱셈기 등 특정 실행 포트(Execution Port)에 명령어가 집중되어 포트 경합이 일어나는 경우입니다.
  • 현상: CPU 클럭은 최대로 돌지만 연산 유닛의 큐가 꽉 차서 추가 명령어를 받지 못함.

2. CPU 캐시 계층 구간 (L1 / L2 / L3 Cache)

CPU 연산 속도(약 0.3ns)에 비해 메인 메모리 DRAM 접근(약 60~100ns)은 수백 배 느리기 때문에, 캐시 적중률(Hit Ratio)이 무너지면 CPU는 정지합니다.

  • 캐시 미스 (L1/L2/LLC Miss)에 의한 메모리 스톨
  • 원인: 데이터 구조가 메모리에 파편화되어 있거나(Linked List, Pointer Chasing), 알고리즘이 순차 메모리 접근(Spatial/Temporal Locality)을 활용하지 못할 때.
  • 현상: CPU 사용률은 높게 잡히지만 실제로는 CPI(Cycles Per Instruction)가 2.0 이상으로 치솟으며 메모리에서 데이터를 가져오길 멍하니 기다리는 상태가 됨.
  • 확인: perf stat -e L1-dcache-load-misses,LLC-load-misses
  • 거짓 공유 (False Sharing) 및 캐시 일관성(MESI 프로토콜) 오버헤드
  • 원인: 서로 다른 스레드가 독립적인 변수를 수정하고 있지만, 그 변수들이 동일한 64바이트 캐시 라인(Cache Line) 안에 묶여 있을 때.
  • 현상: 한 코어가 값을 쓸 때마다 다른 코어들의 L1/L2 캐시 라인이 강제로 무효화(Invalidate)되면서, 코어 간 캐시 버스 트래픽이 폭증하고 CPU가 극단적으로 느려집니다.

3. 인터커넥트(UPI) 및 NUMA 메모리 구간

듀얼 소켓 이상 서버 환경에서 CPU 성능을 가장 크게 갉아먹는 아키텍처 병목입니다.

  • 원격 NUMA 메모리 접근 (Remote Memory Access)
  • 원인: 소켓 0에 위치한 CPU 코어가 소켓 1의 메모리 컨트롤러에 연결된 DRAM의 데이터를 읽고 쓸 때.
  • 현상: 로컬 메모리 접근 대비 지연 시간이 2~3배 늘어나며, 데이터가 UPI(Ultra Path Interconnect) 링크를 건너가느라 대역폭 한계에 봉착합니다.
  • 확인: numastat -c <프로세스명> (numa_missforeign 카운트 증가 확인).
  • 메모리 컨트롤러 대역폭 포화 (Memory Bandwidth Saturation)
  • 원인: StarRocks의 대규모 인메모리 스캔이나 대규모 벡터 연산 등 메모리 집약적 워크로드가 채널 대역폭 한계치(예: 8채널 DDR5 한계)를 모두 소진할 때.
  • 현상: 코어 사용률이 100%가 되기 전에 이미 메모리 버스가 꽉 차서 추가 스레드를 늘려도 처리량이 전혀 증가하지 않는 선형성 단절 현상 발생.

4. OS 커널 및 스케줄러 구간

하드웨어 위에서 프로세스와 스레드를 조율하는 리눅스 커널 스택에서 발생하는 병목입니다.

  • 과도한 컨텍스트 스위칭 (Context Switching)
  • 원인: 물리 CPU 코어 수보다 훨씬 많은 수백~수천 개의 스레드를 생성하여 락(Lock)을 잡거나 I/O 대기를 반복할 때.
  • 현상: CPU가 실제 애플리케이션 코드를 실행하는 시간보다 레지스터 상태를 저장/복원하고 커널 스케줄러(CFS)를 실행하는 데 대부분의 사이클을 낭비함.
  • 확인: vmstat 1에서 cs(Context Switch) 수치가 초당 수십만 이상으로 치솟거나, pidstat -w로 자발적/비자발적 스위칭 확인.
  • 커널 스핀락 (Spinlock) 및 락 경합 (Lock Contention)
  • 원인: 여러 스레드가 공유 메모리나 전역 락(Global Mutex), 파일 디스크립터 테이블, 메모리 맵(mmap_lock)을 동시에 수정하려 할 때.
  • 현상: top에서 커널 모드 CPU 점유율을 나타내는 %sy(System CPU)가 30~50% 이상 급증합니다. 코어가 일은 안 하고 락이 풀릴 때까지 빈 루프를 돌며 타인을 기다리는 상태입니다.
  • 확인: perf top을 실행했을 때 상위 심볼에 queued_spin_lock_slowpath, native_queued_spin_lock_slowpath가 1위를 차지함.
  • 인터럽트 처리 경합 (%si / SoftIRQ)
  • 원인: 초당 패킷 수가 많은 네트워크 트래픽 처리나 고속 NVMe I/O 인터럽트가 특정 CPU 코어로 몰릴 때.
  • 현상: 해당 코어는 유저 코드를 전혀 실행하지 못하고 인터럽트 핸들링에만 묶여 병목의 시발점이 됨.

5. 컨테이너 런타임 및 K8s Cgroups 제약 구간

하드웨어와 OS는 여유가 있는데 K8s의 리소스 격리 정책 때문에 인위적으로 CPU가 멈추는 구간입니다.

  • CFS Bandwidth Throttling (CPU 스로틀링)
  • 원인: Pod의 resources.limits.cpu 설정 시 리눅스 CFS 스케줄러는 기본 100ms(cpu.cfs_period_us=100000) 주기로 할당량을 체크합니다. 멀티스레드 앱이 주기 시작 20ms 만에 할당된 CPU 시간을 다 써버리면, 남은 80ms 동안 프로세스를 강제로 정지(Freeze)시킵니다.
  • 현상: 노드 전체 CPU 사용률은 20%도 안 되는데, 해당 Pod의 응답 지연 시간(P99 Latency)이 극단적으로 튐.
  • 확인:
cat /sys/fs/cgroup/cpu/kubepods/pod.../cpu.stat
# nr_throttled 및 throttled_time 수치 급증 여부 확인
  • 불균등한 CPU 코어 토폴로지 배치 (NUMA 분산 스케줄링)
  • 원인: Kubelet 기본 스케줄러가 단일 Pod에 8개 코어를 할당할 때 소켓 0에서 4개, 소켓 1에서 4개를 쪼개서 할당할 때.
  • 현상: 스레드 풀 내부 통신 시 상시 L3 캐시 미스와 UPI 크로스 트래픽이 유발되어 연산 효율 저하.

CPU 병목 진단 매트릭스

병목 증상의심 구간핵심 확인 지표 / 툴해결 방안
CPU %us 100%이나 IPC 극히 낮음캐시 미스 / DRAM 대기perf stat (IPC < 1.0, LLC-misses)데이터 메모리 지역성 개선, HugePages 적용
CPU %sy(System) 급증락 경합 / 시스템 콜perf top (queued_spin_lock), strace락 세분화, I/O 버퍼링, Connection Pool 튜닝
CPU %si(SoftIRQ) 급증네트워크/NIC 패킷 쏠림mpstat -P ALL 1, /proc/interruptsRPS/RSS 해시 분산, NIC 코어 바인딩
노드 한산한데 Pod 지연 발생K8s CFS 스로틀링cgroup cpu.stat (throttled_time)CPU Limit 제거 or Guaranteed QoS(정수 정적 바인딩)
특정 소켓만 CPU 포화NUMA 불균형numastat, lscpuPod 리소스 크기 단일 NUMA 노드로 재조정

===

대규모 분산 환경(K8s, Cilium, 고성능 스토리지/DB, AI 서빙)에서 실무적으로 성능 저하가 가장 빈번하게 터지는 지점과 튜닝을 통해 성능을 극적으로 끌어올릴 수 있는 핵심 개선 구간을 네트워크와 CPU 각각 우선순위별로 분류했습니다.


1. Network: 성능 저하 빈도 및 개선 여지 분석

네트워크 성능 저하의 약 80%는 양 끝단(하드웨어 버퍼/큐와 커널/CNI 인터페이스)에서 발생합니다.

[빈도/개선 여지 최상]  NIC Ring Buffer & RSS ──> 커널 SoftIRQ / NAPI
[빈도/개선 여지 상]    CNI 데이터 플레인 (veth vs eBPF Host Routing)
[빈도/개선 여지 중상]  소켓 버퍼 (rmem/wmem & ZeroWindow)
[빈도 중 / 개선 난이도 상] ToR 스위치 Incast & 패킷 드롭

① 가장 흔하게 터지고 개선 효과가 가장 큰 구간 (Tier 1)

  • NIC 하드웨어 링 버퍼(Ring Buffer) 및 드롭
  • 상황: OS 설치 기본값은 링 버퍼 크기가 512나 1024 등 보수적으로 잡혀 있습니다. 순간적인 트래픽 버스트(Spark 셔플, S3 업로드) 인입 시 하드웨어 레벨에서 패킷을 즉시 버립니다.
  • 개선 여지: 매우 큼 (작업 비용 매우 낮음).
  • 조치: ethtool -G <NIC> rx 4096 tx 4096 적용만으로 재전송으로 인한 네트워크 지연이 즉시 80% 이상 해소됩니다.
  • RSS(Receive Side Scaling) 해시 불균형 및 SoftIRQ(%si) 특정 코어 쏠림
  • 상황: 25G/100G NIC에서 트래픽의 IP/Port 패턴이 단순하면 특정 1개 CPU 코어만 %si 100%를 치면서 나머지 코어가 놀고 있어도 패킷이 드롭됩니다.
  • 개선 여지: 매우 큼.
  • 조치: NIC 다중 큐(Queue) 확장(ethtool -L), IRQ 코어 친화도(Affinity) 분산, net.core.netdev_budget 상향.

② K8s/CNI 환경 특화 병목 구간 (Tier 2)

  • CNI veth 페어 오버헤드 vs eBPF Host Routing
  • 상황: 기본 CNI 구성(표준 Linux Bridge/veth)은 패킷이 파드로 들어갈 때 호스트 커널 스택과 파드 커널 스택을 이중으로 거치며 컨텍스트 스위칭과 메모리 복사를 유발합니다.
  • 개선 여지: 큼 (Cilium 최적화 핵심).
  • 조치: Cilium Native Routing + eBPF Host Routing(Bypass veth) 적용 시 네트워크 지연 시간을 물리 베어메탈 수준으로 단축(최대 2~3배 레이턴시 개선).
  • 소켓 버퍼 크기 부족에 따른 TCP ZeroWindow 현상
  • 상황: 100G 고속망에서 RTT가 조금만 발생해도 커널 기본 rmem/wmem이 작아 윈도우가 닫히며(ZeroWindow) 대역폭이 1/10 토막 납니다.
  • 개선 여지: 중상.
  • 조치: net.ipv4.tcp_rmem, net.core.rmem_max를 16MB 수준으로 상향하여 BDP(Bandwidth-Delay Product) 확보.

③ 인프라/하드웨어 구간 (Tier 3)

  • ToR 스위치 Incast & MTU 불일치
  • 상황: 스위치 버퍼 오버플로우나 MTU 1500/9000 불일치로 인한 단편화(Fragmentation).
  • 개선 여지: 중간(스위치 설정 및 네트워크 엔지니어와의 협업 필요).

2. CPU: 성능 저하 빈도 및 개선 여지 분석

CPU는 "K8s 인위적 제한(CFS)", "듀얼 소켓 간 메모리 이동(NUMA)", "락(Lock) 경합" 세 지점에서 실무 병목이 집중됩니다.

[빈도 최상 / 개선 즉각적] K8s CFS Bandwidth Throttling (CPU Limit)
[빈도 상 / 성능 개선 큼]  NUMA 원격 메모리 참조 & UPI 버스 경합
[빈도 상 / 해결 난이도 상] 커널 Spinlock 경합 & 컨텍스트 스위칭
[빈도 중 / 구조적 개선]   캐시 미스 (L3 Cache Miss / False Sharing)

① 가장 빈번하고 즉각적인 개선이 나타나는 구간 (Tier 1)

  • K8s CFS Bandwidth Throttling (CPU Limit에 의한 강제 정지)
  • 상황: 노드 CPU 사용률은 20~30%로 널널한데, Pod의 limits.cpu 때문에 100ms 주기 내에서 할당량을 다 썼다고 커널이 프로세스를 50~80ms 동안 완전히 얼려버림(Freeze). Pod P99 응답 지연이 급증하는 1위 원인입니다.
  • 개선 여지: 최상 (설정 즉시 효과 체감).
  • 조치:
  1. 지연 시간에 민감한 코어 엔진(StarRocks, vLLM, DB)은 limits.cpu를 제거하거나,
  2. Kubelet static CPU Manager를 통해 Guaranteed QoS (Requests == Limits, 정수 코어)로 배포하여 CFS 쿼터 메커니즘을 완전히 우회.

② 아키텍처 레벨에서 성능을 대폭 끌어올리는 구간 (Tier 2)

  • 크로스 소켓 통신 (Cross-Socket / Remote NUMA Access)
  • 상황: 소켓 0의 CPU 코어가 소켓 1에 연결된 메모리를 읽거나, 소켓 1의 PCIe에 꽂힌 NIC 패킷을 처리할 때. UPI 인터커넥트 대역폭이 포화되며 레이턴시가 2~3배 증가합니다.
  • 개선 여지: 매우 큼 (인메모리 DB, vLLM, 고속 스토리지 필수).
  • 조치:
  • Kubelet --topology-manager-policy=single-numa-node 설정.
  • 노드당 거대한 단일 Pod 대신 소켓 크기에 맞춘 복수 Pod(예: 노드당 StarRocks BE 2개)로 분할 배치.
  • numa_balancing=0 비활성화로 불필요한 백그라운드 페이지 마이그레이션 방지.

③ 시스템/소프트웨어 레벨 구간 (Tier 3)

  • 커널 스핀락(Spinlock) 경합 (%sy 급증)
  • 상황: 멀티스레드가 과도하게 생성되어 메모리 락(mmap_lock), 커널 소켓 테이블, 파일 디스크립터 경합으로 빈 루프를 돌며 대기.
  • 개선 여지: 중간 (애플리케이션 스레드 풀 튜닝 필요).
  • 조치: 불필요하게 많은 워커 스레드 수 축소(물리 코어 수 기반 정렬), DB 커넥션 풀 크기 제한, Jemalloc 같은 멀티스레드 친화적 메모리 할당자 사용.
  • 캐시 미스 (L3 Cache Miss)
  • 상황: 데이터 지역성이 떨어져 L1/L2/L3 캐시를 다 놓치고 메인 메모리(DRAM)를 계속 조회하여 CPU 파이프라인이 정지(Stall).
  • 개선 여지: 알고리즘 및 코드 레벨 개선 영역. 인프라 관점에서는 HugePages(2MB/1GB)를 활성화하여 TLB(Translation Lookaside Buffer) 미스를 줄여주는 방식으로 5~15% 성능 개선 가능.

실무 최적화 우선순위 요약

영역최우선 작업 대상 (Quick Win)튜닝 대상 파라미터 / 아키텍처예상 효과
Network #1NIC Ring Buffer 최대화ethtool -G <eth> rx 4096 tx 4096버스트 트래픽 시 패킷 드롭 및 재전송 즉시 제거
Network #2CNI eBPF 바이패스Cilium Native Routing + BPF Host Routingveth/iptables 오버헤드 제거, 레이턴시 극소화
Network #3TCP 수신 버퍼 확장net.core.rmem_max, tcp_rmem 16MB25G/100G 환경에서 Throughput 저하(ZeroWindow) 방지
CPU #1K8s CPU Throttling 제거Pod Guaranteed QoS (정수 코어 바인딩)P99 레이턴시 스파이크 및 간헐적 프리징 해결
CPU #2NUMA 도메인 로컬화Kubelet Single-NUMA 정책, 소켓별 Pod 분할L3 캐시 히트율 극대화, UPI 링크 병목 제거
CPU #3메모리 TLB 미스 축소OS & Pod 레벨 HugePages 2MB 할당대용량 메모리(DB, vLLM, MinIO) 주소 변환 오버헤드 감소

===

노드 인수 직후 또는 성능 이상 감지 시 네트워크 버퍼/드롭, CPU 스로틀링, NUMA 크로스 접근, 커널 락 경합을 일괄 점검하는 올인원 쉘 스크립트(check_bottlenecks.sh)입니다.

의존성 패키지가 없는 환경에서도 커널 가상 파일시스템(/proc, /sys)을 직접 파싱하여 동작하며, 판정 결과(PASS/WARN/FAIL)와 함께 즉시 개선 가이드를 출력합니다.


성능 병목 자동 진단 스크립트 (check_bottlenecks.sh)

#!/usr/bin/env bash
#
# Kubernetes Node Performance & Bottleneck Checker
# Target OS: RHEL 10.x / Rocky / AlmaLinux / CentOS Stream
#

set -o pipefail

# ANSI 색상 코드 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'

print_header() {
    echo -e "\n${BLUE}${BOLD}================================================================${NC}"
    echo -e "${BLUE}${BOLD}  $1${NC}"
    echo -e "${BLUE}${BOLD}================================================================${NC}"
}

report_pass() { echo -e "  [${GREEN}PASS${NC}] $1"; }
report_warn() { echo -e "  [${YELLOW}WARN${NC}] $1"; }
report_fail() { echo -e "  [${RED}FAIL${NC}] $1"; }

if [ "$EUID" -ne 0 ]; then
    echo -e "${RED}루트(root) 권한으로 실행해야 전체 항목을 점검할 수 있습니다.${NC}"
    exit 1
fi

print_header "1. 네트워크 계층 병목 진단 (NIC, Ring Buffer, Drop)"

# 물리 NIC 인터페이스 자동 탐색 (가상 인터페이스/루프백 제외)
PHYSICAL_NICS=$(ip -o link show | awk -F': ' '{print $2}' | grep -vE '^(lo|veth|docker|cbr|flannel|cilium|lxc|cali|br-)')

for NIC in $PHYSICAL_NICS; do
    # 상태가 UP인 인터페이스만 점검
    OPERSTATE=$(cat /sys/class/net/"$NIC"/operstate 2>/dev/null)
    if [ "$OPERSTATE" != "up" ]; then
        continue
    fi

    echo -e "\n  * 인터페이스: ${BOLD}$NIC${NC}"

    # 1-1. 링 버퍼(Ring Buffer) 점검 (ethtool 필요)
    if command -v ethtool &>/dev/null; then
        RING_INFO=$(ethtool -g "$NIC" 2>/dev/null)
        if [ -n "$RING_INFO" ]; then
            CURR_RX=$(echo "$RING_INFO" | awk '/Current Hardware Settings:/,/^$/' | awk '/RX:/{print $2}' | head -1)
            MAX_RX=$(echo "$RING_INFO" | awk '/Pre-set maximums:/,/^$/' | awk '/RX:/{print $2}' | head -1)
            
            if [ -n "$CURR_RX" ] && [ -n "$MAX_RX" ]; then
                if [ "$CURR_RX" -eq "$MAX_RX" ]; then
                    report_pass "Rx Ring Buffer가 최댓값($CURR_RX)으로 설정되어 있습니다."
                else
                    report_warn "Rx Ring Buffer가 최댓값보다 작습니다. (현재: $CURR_RX / 최대: $MAX_RX)"
                    echo -e "         ${YELLOW}-> 개선 조치: ethtool -G $NIC rx $MAX_RX tx $MAX_RX${NC}"
                fi
            fi
        fi

        # 1-2. NIC 하드웨어 패킷 드롭 및 에러 카운터 점검
        NIC_DROPS=$(ethtool -S "$NIC" 2>/dev/null | grep -iE 'rx_dropped|rx_missed_errors|rx_discards|drop' | awk '$2 > 0 {print $1, $2}')
        if [ -z "$NIC_DROPS" ]; then
            report_pass "NIC 하드웨어 버퍼 레벨 패킷 드롭/에러가 없습니다 (0)."
        else
            report_fail "NIC 하드웨어 패킷 드롭이 감지되었습니다:\n$NIC_DROPS"
            echo -e "         ${RED}-> 개선 조치: 링 버퍼 확장 및 NIC 대역폭/스위치 Flow Control 점검 필요${NC}"
        fi
    else
        report_warn "ethtool이 설치되어 있지 않아 링 버퍼 점검을 생략합니다."
    fi

    # 1-3. OS 레벨 인터페이스 드롭 확인
    OS_RX_DROP=$(cat /sys/class/net/"$NIC"/statistics/rx_dropped 2>/dev/null || echo 0)
    if [ "$OS_RX_DROP" -eq 0 ]; then
        report_pass "OS 수신 패킷 드롭 통계 정상 (rx_dropped: 0)"
    else
        report_warn "OS 수신 패킷 드롭 발생 이력 (rx_dropped: $OS_RX_DROP)"
    fi
done

# 1-4. 커널 NAPI SoftIRQ time_squeeze 점검
if [ -f /proc/net/softnet_stat ]; then
    TIME_SQUEEZE=$(awk '{sum += hex2dec("0x"$3)} END {print sum}' /proc/net/softnet_stat 2>/dev/null || \
                   awk '{print "0x"$3}' /proc/net/softnet_stat | while read h; do printf "%d\n" $h; done | awk '{sum+=$1} END {print sum}')
    if [ "$TIME_SQUEEZE" -eq 0 ]; then
        report_pass "NAPI SoftIRQ 버짓 초과(time_squeeze) 없음 (0)"
    else
        report_warn "SoftIRQ 처리 시간 초과 이력 발생 ($TIME_SQUEEZE 건)"
        echo -e "         ${YELLOW}-> 개선 조치: sysctl -w net.core.netdev_budget=600${NC}"
    fi
fi

# 1-5. TCP 수신 버퍼(rmem) 설정 점검
RMEM_MAX=$(sysctl -n net.core.rmem_max 2>/dev/null || echo 0)
if [ "$RMEM_MAX" -ge 16777216 ]; then
    report_pass "TCP 최대 수신 버퍼가 고속망에 맞게 설정됨 ($(( RMEM_MAX / 1024 / 1024 )) MB)"
else
    report_warn "TCP 최대 수신 버퍼(net.core.rmem_max)가 작습니다 ($(( RMEM_MAX / 1024 )) KB)"
    echo -e "         ${YELLOW}-> 개선 조치: sysctl -w net.core.rmem_max=16777216 (16MB 권장)${NC}"
fi

print_header "2. CPU 계층 병목 진단 (K8s Throttling, NUMA, Spinlock)"

# 2-1. K8s Cgroup CPU Throttling 발생 여부 점검
CGROUP_THROTTLED=0
# Cgroups v1 & v2 호환 탐색
for STAT_FILE in $(find /sys/fs/cgroup -name "cpu.stat" 2>/dev/null); do
    THROTTLED=$(awk '/nr_throttled/{print $2}' "$STAT_FILE" 2>/dev/null)
    if [ -n "$THROTTLED" ] && [ "$THROTTLED" -gt 0 ]; then
        CGROUP_THROTTLED=$((CGROUP_THROTTLED + THROTTLED))
    fi
done

if [ "$CGROUP_THROTTLED" -eq 0 ]; then
    report_pass "컨테이너 CPU Throttling(강제 프리징) 발생 내역 없음"
else
    report_fail "컨테이너 CPU Throttling 발생 감지 (누적 $CGROUP_THROTTLED 회)"
    echo -e "         ${RED}-> 주요 워크로드(StarRocks, vLLM)의 CPU Limits 제거 또는 Guaranteed QoS 적용 필요${NC}"
fi

# 2-2. NUMA 밸런싱 및 원격 메모리 미스(Remote Miss) 점검
NUMA_NODES=$(ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l)
if [ "$NUMA_NODES" -gt 1 ]; then
    echo -e "  * 멀티 NUMA 노드 감지: 총 ${BOLD}$NUMA_NODES${NC}"
    
    AUTO_NUMA=$(sysctl -n kernel.numa_balancing 2>/dev/null || echo 0)
    if [ "$AUTO_NUMA" -eq 0 ]; then
        report_pass "자동 커널 NUMA 밸런싱 비활성화됨 (numa_balancing=0, 레이턴시 튐 방지)"
    else
        report_warn "자동 커널 NUMA 밸런싱이 켜져 있습니다 (numa_balancing=1)"
        echo -e "         ${YELLOW}-> DB/스토리지 지연 방지를 위해 'sysctl -w kernel.numa_balancing=0' 권장${NC}"
    fi

    if command -v numastat &>/dev/null; then
        NUMA_MISS=$(numastat 2>/dev/null | awk '/numa_miss/{sum += $2 + $3} END {print sum}')
        if [ -n "$NUMA_MISS" ] && [ "${NUMA_MISS%.*}" -gt 100000 ]; then
            report_warn "NUMA 원격 메모리 미스 누적 다수 ($NUMA_MISS 건)"
            echo -e "         ${YELLOW}-> Kubelet --topology-manager-policy=single-numa-node 적용 검토${NC}"
        else
            report_pass "NUMA 원격 메모리 할당 상태 양호"
        fi
    fi
else
    report_pass "단일 NUMA 노드 구조 (크로스 소켓 페널티 없음)"
fi

# 2-3. 커널 스핀락 및 시스템 CPU(%sy) 실시간 샘플링 (1초)
if command -v mpstat &>/dev/null; then
    CPU_SY=$(mpstat 1 1 | awk '/Average:/ {print $5}')
    if (( $(echo "$CPU_SY > 20.0" | bc -l 2>/dev/null || [ "${CPU_SY%.*}" -gt 20 ] && echo 1 || echo 0) )); then
        report_warn "시스템 커널 CPU 점유율(%sy)이 높습니다 (${CPU_SY}%)"
        echo -e "         ${YELLOW}-> 락(Spinlock) 경합 또는 과도한 시스템 콜 의심 (perf top 점검 필요)${NC}"
    else
        report_pass "시스템 커널 CPU 점유율 안정적 (${CPU_SY}%)"
    fi
fi

print_header "3. 커널 시스템 파라미터 & 테이블 용량 점검"

# 3-1. Conntrack 테이블 여유량 점검
if [ -f /proc/sys/net/netfilter/nf_conntrack_count ]; then
    CONN_COUNT=$(cat /proc/sys/net/netfilter/nf_conntrack_count)
    CONN_MAX=$(cat /proc/sys/net/netfilter/nf_conntrack_max)
    USAGE_PCT=$(( CONN_COUNT * 100 / CONN_MAX ))

    if [ "$USAGE_PCT" -lt 70 ]; then
        report_pass "Conntrack 테이블 사용률 정상 (${USAGE_PCT}%, $CONN_COUNT / $CONN_MAX)"
    else
        report_fail "Conntrack 테이블 포화 위험 (${USAGE_PCT}%, $CONN_COUNT / $CONN_MAX)"
        echo -e "         ${RED}-> 개선 조치: sysctl -w net.netfilter.nf_conntrack_max=2097152${NC}"
    fi
fi

# 3-2. 파일 디스크립터(File Descriptors) 점검
FILE_NR=$(cat /proc/sys/fs/file-nr | awk '{print $1}')
FILE_MAX=$(cat /proc/sys/fs/file-nr | awk '{print $3}')
FILE_USAGE_PCT=$(( FILE_NR * 100 / FILE_MAX ))

if [ "$FILE_USAGE_PCT" -lt 70 ]; then
    report_pass "시스템 파일 디스크립터 여유 (사용량: $FILE_NR / 최대: $FILE_MAX)"
else
    report_warn "파일 디스크립터 사용률 높음 (${FILE_USAGE_PCT}%)"
    echo -e "         ${YELLOW}-> 개선 조치: sysctl -w fs.file-max=20971520${NC}"
fi

# 3-3. 비대칭 라우팅 및 BGP ECMP 필터 (rp_filter)
RP_FILTER=$(sysctl -n net.ipv4.conf.all.rp_filter 2>/dev/null)
if [ "$RP_FILTER" -eq 2 ]; then
    report_pass "BGP/ECMP 및 비대칭 라우팅용 rp_filter 정상 설정됨 (Loose Mode: 2)"
else
    report_warn "rp_filter가 Strict(1) 또는 미설정($RP_FILTER) 상태입니다."
    echo -e "         ${YELLOW}-> BGP 다중 경로 패킷 드롭 방지: sysctl -w net.ipv4.conf.all.rp_filter=2${NC}"
fi

print_header "진단 완료: 이상 징후([WARN]/[FAIL]) 항목을 우선 조치하세요."

스크립트 실행 방법

  1. 노드에 파일 생성 및 실행 권한 부여:
cat << 'EOF' > /usr/local/bin/check_bottlenecks.sh
# (위 스크립트 내용 붙여넣기)
EOF
chmod +x /usr/local/bin/check_bottlenecks.sh
  1. 루트 권한으로 실행:
sudo /usr/local/bin/check_bottlenecks.sh
  1. 결과 해석 가이드:
  • Rx Ring Buffer 경고 시: 즉시 ethtool -G <NIC> rx 4096 tx 4096를 적용하고 /etc/sysconfig/network-scripts/ 또는 Systemd 서비스에 영구 반영합니다.
  • CPU Throttling 실패 시: 지연 시간에 민감한 Pod(StarRocks, vLLM, PostgreSQL) 매니페스트에서 CPU Limit을 제거하거나 Kubelet static CPU Manager를 통해 Guaranteed QoS로 배포합니다.
  • SoftIRQ time_squeeze 경고 시: 초당 패킷 수가 NAPI 예산을 넘어서고 있으므로 net.core.netdev_budget=600으로 완화합니다.

===

운영 중인 K8s 워커 노드 및 베어메탈 시스템에서 부하를 주지 않고 실시간으로 NUMA 토폴로지 균형, 원격 메모리 참조(Remote Access), 커널 자동 밸런싱 영향도, 주요 Pod/프로세스별 NUMA 바인딩 상태를 진단하는 모니터링 스크립트(check_numa_efficiency.sh)입니다.


NUMA 운영 효율성 진단 스크립트 (check_numa_efficiency.sh)

#!/usr/bin/env bash
#
# NUMA Operational Efficiency & Locality Checker for Production Nodes
# Target OS: RHEL 9.x/10.x, Rocky, CentOS Stream
#

set -o pipefail

# 색상 정의
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'

print_header() {
    echo -e "\n${BLUE}${BOLD}================================================================${NC}"
    echo -e "${BLUE}${BOLD}  $1${NC}"
    echo -e "${BLUE}${BOLD}================================================================${NC}"
}

report_pass() { echo -e "  [${GREEN}PASS${NC}] $1"; }
report_warn() { echo -e "  [${YELLOW}WARN${NC}] $1"; }
report_info() { echo -e "  [${BLUE}INFO${NC}] $1"; }

if [ "$EUID" -ne 0 ]; then
    echo -e "${RED}정확한 프로세스 cgroup 및 메모리 조회를 위해 root 권한으로 실행하세요.${NC}"
    exit 1
fi

# 1. NUMA 기본 하드웨어 토폴로지 점검
print_header "1. NUMA 토폴로지 및 메모리 사용률 분포"

NUMA_COUNT=$(ls -d /sys/devices/system/node/node[0-9]* 2>/dev/null | wc -l)
if [ "$NUMA_COUNT" -le 1 ]; then
    report_info "단일 NUMA 노드 시스템입니다. (크로스 소켓 통신 오버헤드 없음)"
    exit 0
fi

echo -e "  * 감지된 NUMA 노드 수: ${BOLD}${NUMA_COUNT}${NC}\n"
printf "  %-10s %-12s %-12s %-12s %-10s\n" "Node" "Total(MB)" "Used(MB)" "Free(MB)" "Usage(%)"
echo "  ------------------------------------------------------------"

NODE_MEM_IMBALANCE=0
MIN_USAGE=100
MAX_USAGE=0

for ((i=0; i<NUMA_COUNT; i++)); do
    MEMINFO="/sys/devices/system/node/node$i/meminfo"
    if [ -f "$MEMINFO" ]; then
        TOTAL_KB=$(awk '/MemTotal/{print $4}' "$MEMINFO")
        FREE_KB=$(awk '/MemFree/{print $4}' "$MEMINFO")
        USED_KB=$((TOTAL_KB - FREE_KB))
        
        TOTAL_MB=$((TOTAL_KB / 1024))
        USED_MB=$((USED_KB / 1024))
        FREE_MB=$((FREE_KB / 1024))
        USAGE_PCT=$((USED_KB * 100 / TOTAL_KB))

        [ "$USAGE_PCT" -gt "$MAX_USAGE" ] && MAX_USAGE=$USAGE_PCT
        [ "$USAGE_PCT" -lt "$MIN_USAGE" ] && MIN_USAGE=$USAGE_PCT

        printf "  %-10s %-12d %-12d %-12d %-9d%%\n" "node$i" "$TOTAL_MB" "$USED_MB" "$FREE_MB" "$USAGE_PCT"
    fi
done

# 노드 간 메모리 불균형 점검 (편차 30% 초과 여부)
IMBALANCE_DIFF=$((MAX_USAGE - MIN_USAGE))
echo ""
if [ "$IMBALANCE_DIFF" -gt 30 ]; then
    report_warn "NUMA 노드 간 메모리 사용 편차가 큽니다. (최대 편차: ${IMBALANCE_DIFF}%)"
    echo -e "         ${YELLOW}-> 특정 소켓에만 워크로드가 편중되어 조기 OOM 또는 Swap 위험이 있습니다.${NC}"
else
    report_pass "NUMA 노드 간 메모리 사용률이 비교적 균등하게 분산되어 있습니다. (편차: ${IMBALANCE_DIFF}%)"
fi

# 2. 커널 자동 NUMA 밸런싱 설정 점검
print_header "2. 커널 NUMA 밸런싱 (Auto NUMA Balancing)"

AUTO_NUMA=$(sysctl -n kernel.numa_balancing 2>/dev/null || echo 0)
if [ "$AUTO_NUMA" -eq 0 ]; then
    report_pass "kernel.numa_balancing = 0 (비활성화 상태: 대규모 분산 환경 권장)"
else
    report_warn "kernel.numa_balancing = 1 (활성화 상태)"
    echo -e "         ${YELLOW}-> StarRocks, MinIO, K8s 환경에서는 백그라운드 페이지 스캔으로 인한 레이턴시 스파이크 유발 가능.${NC}"
    echo -e "         ${YELLOW}-> 조치: sysctl -w kernel.numa_balancing=0${NC}"
fi

# 3. 원격 메모리 접근(Remote Memory Access) 통계 실측
print_header "3. NUMA 메모리 할당 효율성 (numastat)"

if [ -f /sys/devices/system/node/node0/numastat ]; then
    TOTAL_HIT=0
    TOTAL_MISS=0
    TOTAL_FOREIGN=0

    for ((i=0; i<NUMA_COUNT; i++)); do
        HIT=$(awk '/numa_hit/{print $2}' "/sys/devices/system/node/node$i/numastat")
        MISS=$(awk '/numa_miss/{print $2}' "/sys/devices/system/node/node$i/numastat")
        FOREIGN=$(awk '/numa_foreign/{print $2}' "/sys/devices/system/node/node$i/numastat")
        
        TOTAL_HIT=$((TOTAL_HIT + HIT))
        TOTAL_MISS=$((TOTAL_MISS + MISS))
        TOTAL_FOREIGN=$((TOTAL_FOREIGN + FOREIGN))
    done

    # 원격 메모리 할당 비율 계산 (Miss Ratio)
    TOTAL_ALLOC=$((TOTAL_HIT + TOTAL_MISS))
    if [ "$TOTAL_ALLOC" -gt 0 ]; then
        MISS_RATIO=$(awk "BEGIN {printf \"%.2f\", ($TOTAL_MISS / $TOTAL_ALLOC) * 100}")
        echo -e "  * 누적 로컬 할당 (numa_hit) : ${TOTAL_HIT}"
        echo -e "  * 누적 원격 할당 (numa_miss): ${TOTAL_MISS} (원격 할당률: ${BOLD}${MISS_RATIO}%${NC})"
        echo -e "  * 타 노드 대리 할당 (foreign): ${TOTAL_FOREIGN}\n"

        if (( $(echo "$MISS_RATIO > 5.0" | bc -l 2>/dev/null || [ "${MISS_RATIO%.*}" -gt 5 ] && echo 1 || echo 0) )); then
            report_warn "원격 메모리 할당률이 5%를 초과합니다 (${MISS_RATIO}%)."
            echo -e "         ${YELLOW}-> 로컬 소켓의 메모리가 부족하여 원격 소켓의 DRAM에 메모리를 할당했습니다 (UPI 트래픽 증가).${NC}"
        else
            report_pass "원격 메모리 할당률이 매우 낮습니다 (${MISS_RATIO}% - 로컬 메모리 적중률 우수)."
        fi
    fi
fi

# 4. 주요 고부하 워크로드(K8s Pod / 프로세스) NUMA 바인딩 진단
print_header "4. 주요 애플리케이션의 NUMA 바인딩 상태 (Top Memory Procs)"

echo -e "  * 메모리 점유 상위 주요 프로세스의 CPU/Memory 노드 격리 상태:\n"
printf "  %-8s %-18s %-16s %-12s %-10s\n" "PID" "COMMAND" "CPUs_ALLOWED" "MEMs_ALLOWED" "ISOLATION"
echo "  ----------------------------------------------------------------------"

# 메모리 사용량 상위 8개 프로세스 추출 (커널 스레드 제외)
TOP_PIDS=$(ps -eo pid,%mem,comm --sort=-%mem | awk '$1 !~ /PID/ && $3 !~ /^\[.*\]$/ {print $1}' | head -n 8)

for PID in $TOP_PIDS; do
    if [ -f "/proc/$PID/status" ]; then
        COMM=$(cat "/proc/$PID/comm" 2>/dev/null | cut -c 1-17)
        CPUS=$(awk -F':\t' '/Cpus_allowed_list/{print $2}' "/proc/$PID/status" 2>/dev/null)
        MEMS=$(awk -F':\t' '/Mems_allowed_list/{print $2}' "/proc/$PID/status" 2>/dev/null)
        
        # Mems_allowed_list가 단일 노드(예: 0 또는 1)인지, 다중 노드(예: 0-1)인지 검증
        if [[ "$MEMS" =~ ^[0-9]+$ ]]; then
            STATUS="${GREEN}Single-NUMA${NC}"
        else
            STATUS="${YELLOW}Cross-NUMA${NC}"
        fi

        printf "  %-8s %-18s %-16s %-12s " "$PID" "$COMM" "$CPUS" "$MEMS"
        echo -e "$STATUS"
    fi
done

echo ""
report_info "Cross-NUMA로 표시된 워크로드는 두 소켓의 메모리를 동시에 참조하여 UPI 지연이 발생할 수 있습니다."
report_info "K8s Pod의 경우 'resources.requests == resources.limits'(정수 코어)로 Guaranteed QoS를 주면 단일 NUMA로 고정됩니다."

# 5. 주요 인터페이스(NIC) PCIe NUMA 지역성 매핑
print_header "5. 주요 물리 NIC의 NUMA 도메인 정렬"

PHYS_NICS=$(ip -o link show | awk -F': ' '{print $2}' | grep -vE '^(lo|veth|docker|cbr|flannel|cilium|lxc|cali|br-)')

for NIC in $PHYS_NICS; do
    if [ -d "/sys/class/net/$NIC/device" ]; then
        NIC_NUMA=$(cat "/sys/class/net/$NIC/device/numa_node" 2>/dev/null || echo "-1")
        SPEED=$(cat "/sys/class/net/$NIC/speed" 2>/dev/null || echo "N/A")
        
        if [ "$NIC_NUMA" -ge 0 ]; then
            echo -e "  * 인터페이스 ${BOLD}$NIC${NC} (${SPEED} Mbps) -> ${GREEN}NUMA Node $NIC_NUMA${NC}에 직결됨"
        else
            echo -e "  * 인터페이스 ${BOLD}$NIC${NC} -> NUMA 노드 식별 불가 ($NIC_NUMA)"
        fi
    fi
done

print_header "진단 완료"

실행 및 해석 방법

1. 스크립트 등록 및 실행

sudo bash check_numa_efficiency.sh

2. 핵심 점검 결과별 조치 가이드

  • 원격 할당률(numa_miss)이 5% 이상으로 높을 때
  • 특정 소켓의 메모리가 먼저 가득 차서 다른 소켓의 메모리를 빌려 쓰는 상태입니다.
  • 워크로드 메모리 크기를 소켓 용량에 맞게 줄이거나, Pod의 복제본을 늘려 소켓별로 분산(podAntiAffinity 또는 Kubelet Single NUMA 설정)해야 합니다.
  • 주요 프로세스가 Cross-NUMA(예: MEMs_allowed_list: 0-1)로 뜰 때
  • 프로세스가 양쪽 소켓의 코어와 메모리를 자유롭게 오가며 UPI 링크 트래픽을 유발하고 있는 상태입니다.
  • StarRocks BE, MinIO, vLLM 같은 고부하 Pod의 YAML에서 requests.cpu == limits.cpu (정수 코어) 및 requests.memory == limits.memory로 맞추어 Kubelet CPU/Topology Manager가 Single-NUMA로 격리하도록 유도합니다.
  • NIC의 NUMA Node와 워크로드 위치 불일치 시
  • 네트워크 패킷이 들어오는 물리 소켓과 이를 처리하는 파드의 메모리 소켓이 다르면 패킷마다 소켓 간 점프가 발생합니다. 트래픽이 가장 많은 파드는 해당 NIC와 동일한 NUMA 노드로 바인딩하는 것이 유리합니다.
profile
engineer

0개의 댓글