9.5.1.1 소켓 버퍼링 및 시스템 콜(System Call) 오버헤드 기반 네트워크 I/O 병목 진단

9.5.1.1 소켓 버퍼링 및 시스템 콜(System Call) 오버헤드 기반 네트워크 I/O 병목 진단

분산 애플리케이션에서 Zenoh 통신망을 수립한 직후, 기대했던 대역폭(Bandwidth)이나 응답 속도(Latency)가 나타나지 않을 때 경험이 부족한 엔지니어들은 대개 코드를 의심하며 직렬화 로직을 뜯어고치려 든다. 그러나 성능 하락의 주범 중 80%는 애플리케이션 레이어가 아닌 호스트 OS의 커널 네트워크 스택, 즉 소켓 버퍼 링(Socket Buffer Ring)의 포화와 빈번한 시스템 콜(System Call) 컨텍스트 스위칭에 기인한다.

본 절에서는 응용 계층의 오류가 아닌 패킷 I/O의 바닥에서 발생하는 커널 스택의 물리적 병목 현상을 정확하게 진단하고, 커널-유저 공간 간의 보이지 않는 오버헤드를 추적하여 최적화 포인트(Tuning Point)를 발굴하는 런북을 전개한다.

1. 커널 소켓 버퍼 포화로 인한 숨겨진 드롭(Drop) 징후

애플리케이션이 z_pub 함수를 아무리 1us(마이크로초) 만에 던지고 탈출한다 해도, 이 데이터는 즉시 전광석화처럼 네트워크 카드로 쏘아지지 않는다. 데이터는 우선 리눅스 커널의 전송 소켓 버퍼(wmem) 내에 적재되며, 반대로 수신된 데이터 역시 수신 버퍼(rmem)에서 애플리케이션이 읽어가기를 기다린다.

드론의 영상 포착 스트림처럼 일시에 대량의 버스트(Burst) 트래픽이 발생했을 때, 커널 소켓 버퍼 크기가 작게 설정되어 있다면 커널은 더 이상 메모리를 할당할 수 없어 하드웨어 레벨에서 패킷을 묵살(Drop)해버린다. 이때 애플리케이션 로그는 너무나 평온하며 어떠한 에러도 토해내지 않는다.

시스템 아키텍트는 아래와 같은 네트워크 통계 명령어를 통해 netstat 혹은 ss의 소켓 큐 상태를 현미경처럼 들여다보아야 한다.

# 운영체제의 시스템 소켓 상태 모니터링
ss -ump

# 출력 예시
State   Recv-Q  Send-Q   Local Address:Port   
ESTAB   120050  0        192.168.1.10:7447

Recv-Q 컬럼의 숫자가 0이 아니라 수만 바이트 이상으로 솟구쳐 있다면, 이는 Zenoh 수신 콜백 스레드의 처리 속도가 커널 버퍼에 패킷이 쌓이는 속도를 도저히 따라가지 못하고 있다는 명백한 질식(Choking) 상태의 방증이다.

2. 빈번한 시스템 콜(System Call)에 의한 유저-커널 교차 오버헤드

또 다른 형태의 은밀한 병목은 I/O를 위해 너무 잦은 개입을 요구하는 시스템 콜의 파동(System call storm) 이다.
애플리케이션 코드가 데이터를 소켓에 밀어 넣거나 꺼내기 위해 sendto() 혹은 recvfrom() API를 호출할 때마다, CPU는 유저 모드(User Mode)에서 커널 모드(Kernel Mode)로 권한 전이를 수행한다. 이 컨텍스트 스위칭 비용은 캐시 라인을 증발시키고 막대한 클럭(Clock) 사이클 파이프라인을 붕괴시킨다.

만약 Zenoh 클라이언트가 1바이트짜리 센서 데이터를 100만 번 따로따로 put 한다고 생각해보자. 네트워크 망이 1MB를 전송하는 대역폭은 충분할지 몰라도, 100만 번의 컨텍스트 스위치가 CPU 점유율(sys)을 100%로 타격해버린다.

이를 진단하기 위해서는 리눅스의 strace를 활용하여 프로세스 시스템 콜 내역을 프로파일링한다.

strace -c -p <zenoh_process_id>

# 결과 분석 
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.81    0.005001           2      2500           epoll_wait
 30.15    0.002512           1      2490           recvfrom
 10.04    0.000841           1       800           sendto

위 내역처럼 recvfrom 콜 횟수와 전체 점유 타임이 기형적으로 높다면, 이 시스템은 배치(Batching) 처리 부재라는 결함을 앓고 있는 것이다. 애플리케이션이 시스템 콜을 줄이기 위해 Zenoh의 자체 내부 큐 사이즈를 늘리고, 다량의 트래픽을 사용자 단에서 묶어내어(Aggregating) 단일 I/O로 떨어뜨려야만 병목이 해소된다는 진단 결과가 도출된다.

3. 네트워크 큐 인터럽트의 CPU 집중 편향

가장 간과하기 쉬운 하드웨어 레이어의 I/O 병목은 멀티 코어 시스템에서의 인터럽트 편향(Interrupt Affinity) 현상이다.
서버는 64코어를 가지고 있는데, 100Gbps 대역의 트래픽을 수신할 때 네트워크 인터페이스 카드(NIC)가 발생시키는 하드웨어 인터럽트(IRQ)가 모조리 CPU 0번 코어 하나에만 매핑되어 있는 경우가 허다하다.

top 또는 htop 명령어로 개별 코어별로 si (Software Interrupt) 수치를 관찰하라. 특정 단일 코어만 si 지수가 99%에 육박하고 나머지 63개 코어가 놀고 있다면, 패킷 수신 자체가 해당 코어의 처리 한계 속도에 갇혀버린 것이다.
이때는 네트워크 엔지니어가 직접 개입하여 리눅스의 irqbalance 데몬을 점검하거나, 수동으로 수신 큐 맵핑(RSS: Receive Side Scaling)을 활성화시켜 인터럽트 트래픽을 가용 코어 전체로 균등하게 쪼개 배분(Sharding)해야 한다. 최상위 분산 컴퓨팅의 퍼포먼스는 이처럼 아주 고전적인 커널과 하드웨어 스택의 물리적 치유 위에서만 꽃필 수 있다.