16.9 성능 진단 및 프로파일링 실무
시스템이 느리다고 불평하는 것은 개발자가 아니라 일반 사용자다. 아키텍트는 “어디서 어떻게 느린지“를 정확한 과학적 근거로 증명해 내야 한다.
이 장은 16장의 모든 최적화 기법들을 적용한 후, 그것이 뇌피셜(추측)이 아닌 실제 물리적 수치로 증명되었는지 뜯어보는 과정이다. 소켓 안을 날아다니는 패킷의 배를 Wireshark 로 갈라보고, CPU 가 땀을 흘리는 부위를 Flamegraph 로 촬영하며, 서버의 피(Memory) 가 새어나가는 환부를 프로파일러로 찾아내는 등, 실리콘 레벨의 성능 감식(Forensics) 런북을 전개한다.
1. Wireshark를 이용한 Zenoh 프로토콜 패킷 레벨 지연 분석
프로그램 로그(stdout) 에 찍히는 시간은 이미 커널 버퍼에서 파싱을 다 끝낸 뒤의 가짜 정답이다. 진짜 병목을 보려면, 패킷이 랜 카드(NIC) 에 부딪힌 절대 시간(Wire-time) 을 캡처해야 한다.
1.0.1 [인스펙션] 패킷 부검(Autopsy) 전술
Zenoh 는 독자적인 프레이밍 룰을 쓰기 때문에(Zenoh 스펙), Wireshark 플러그인 설정이 없으면 그저 “TCP 쓰레기 데이터” 로 보인다.
1. Zenoh Dissector(분해기) 설치 및 인터셉트
Wireshark (또는 tshark) 에 최신 Zenoh 디섹터 플러그인(.lua) 을 주입한다.
## 서버에서 7447 포트로 오가는 1만 개의 패킷을 덤프(pcap) 뜬다.
sudo tcpdump -i eth0 port 7447 -w zenoh_trace.pcap -c 10000
이 덤프 파일을 개발자 데스크탑의 Wireshark 에 넣고, zenoh 필터를 걸어라.
2. 삼각 함수(Delta Time) 추적 런북
- 로봇 머신의 tcpdump 와 서버 머신의 tcpdump 를 동시에 켠다.
- 패킷 로스 증거: Wireshark 에서 검정 바디에 빨간 글씨(
TCP Retransmission이나Dup ACK) 가 보이면, Zenoh 코드를 볼 게 아니라 앞서 배운 커널 튜닝(16.2장 BBR, 큐 사이즈) 을 안 해서 통신망 자체가 찢어진 것이다. - 프래그멘테이션 증거:
Frame탭을 눌러서 한 패킷의 크기가 1514바이트를 꽉 채우고(IPv4 Fragmented IP protocol) 꼬리를 물고 있다면, 페이로드가 커서 강제 토막 난 것이다. Zenoh 압축(16.6장) 이나 Batching 최적화(16.5장) 가 시급하다.
2. Flamegraph와 perf를 활용한 CPU 병목 구간(Hotspot) 탐색
데이터는 빛살처럼 쏟아지는데, 특정 로봇이나 서버의 CPU 가 100%를 치면서 시스템 전체를 늪으로 끌고 간다. 소스 코드가 수십만 줄인데 범인(Bottleneck) 함수를 어떻게 찾을 것인가?
2.0.1 [Runbook] 불꽃 지도(Flamegraph) 소각 전술
리눅스의 신(Linus Torvalds) 들이 만든 가장 극단적인 프로파일링 툴, perf 다.
1. 콜 스택(Call Stack) 초당 99회 샘플링
CPU 가 헉헉대는 바로 그 순간, 터미널을 열고 기동 된 zenohd (혹은 당신의 로봇 프로세스) 의 뒤통수를 후려친다.
## 60초 동안 프로세스(PID: 1234) 가 무슨 함수를 실행하고 있는지 99Hz 로 몰래 찍는다.
sudo perf record -F 99 -p 1234 -g -- sleep 60
2. SVG 플레임 그래프 추출
이 막대한 데이터를 시각화 도구(Brendan Gregg의 FlameGraph) 로 넘겨 불꽃 모양의 지도를 뽑아낸다.
- 해석 런북: 지도에 가장 넙데데하고 평평하게 공간을 많이 차지한 박스(함수) 가, CPU 사이클을 가장 많이 갉아먹은 범인이다.
- 만약 그 박스 이름이
zenoh::net::crypto::aes_encrypt라면 암호화 부하가 한계에 달한 것이며(16.7장의 하드웨어 가속 미적용),std::sync::mutex::lock이 떡하니 자리 잡고 있다면 개발자가 락(Lock) 을 잘못 걸어서(16.8장) 스레드 풀 전체가 병목에 빠진 것이다. 이것은 추측이 아닌 명백한 물리적 증거다.
3. 메모리 릭(Memory Leak) 탐지 및 힙(Heap) 프로파일링
테스트 환경에서 1시간 돌렸을 때는 멀쩡하던 시스템이, 공장에서 1주일을 켜두니 RAM 32GB를 혼자 전부 집어삼키고 OS 에 의해 모가지가 날아갔다(OOM Killer).
3.0.1 [인스펙션] 메모리 뱀파이어 발본색원 전술
Zenoh 본체(Rust 코어) 는 메모리 릭이 일어날 확률이 0%에 수렴한다. 진짜 범인은 당신이 붙여놓은 Python/C++ 바인딩 콜백 함수 내부나, 끊기지 않는 무한 큐 레이어다.
1. Valgrind 및 Massif 구동 (C/C++ 도메인)
당신이 짠 C/C++ 로봇 제어기를 로딩할 때, 메모리 고문 도구를 붙여라.
valgrind --tool=massif ./my_robot_node
프로그램이 끝난 뒤 덤프 파일을 ms_print 로 까보면, 시간에 따라 계단식으로(_/?/_/?) 영원히 상승하기만 하는 힙 메모리 변수명이 정확히 찍혀 나온다. 백프레셔(Backpressure) 처리 실패로 인한 무한 큐 잉여 객체가 99% 의 원인이다.
2. Rust Jemalloc / Heaptrack 연동
Zenoh 로 자체 컴파일한 커스텀 데몬의 경우, heaptrack 을 붙여서 실시간으로 malloc() 이 불린 횟수와 free() 된 횟수를 카운팅하라. 이 차이가 0 이 아니라면, 어딘가에서 Zenoh ZBytes 버퍼의 래퍼런스 카운트(Reference Count, Arc) 가 당신의 코드 실수로 떨어지지 않아 가비지가비 컬렉팅에서 제외된 채 영원히 우주 미아로 쌓이고 있는 것이다.
4. 성능 최적화 전-후 비교 리포트 작성 및 지속적 통합(CI) 환경의 성능 회귀 테스트
모든 16장의 최적화가 성공적으로 끝났다고 안심하는 순간, 다음 주에 입사한 신입 개발자가 “코드 구조를 리팩토링했습니다” 라며 당신이 비틀어 놓은 메모리 구조를 다 망쳐놓는다(Performance Regression).
4.0.1 [Runbook] 성능 방어선(CI/CD) 자동화 전술
기능(Feature) 이 고장 난 것만 테스트하지 마라. 속도(Speed) 가 떨어지는 것도 컴파일 실패로 규정해야 한다.
1. Zenoh 벤치마크 자동화 파이프라인 (GitHub Actions)
매일 새벽 3시에 텅 빈 클라우드 인스턴스를 2대 띄운다 (노드 A, 노드 B).
- 코드가 머지(Merge) 된 최신 Zenoh 라우터 빌드를 돌린다.
- 자동으로 CI 스크립트가
z_ping과z_perf를 각각 구동하여 1분간 스루풋을 때린다.
2. 하드 파산(Threshold) 리포팅
- 테스트가 끝나면 나오는 결과값(
Throughput,Latency) 을 어제 기록해 둔 수치(Baseline) 와 비교한다. - “어제 통과 스루풋은 1.5GB/s 였는데, 오늘 신입 코드 머지 이후 0.8GB/s 로 반 토막 났습니다!”
- 이 즉시 해당 커밋(Commit) 의 체크 마크를 빨간 엑스박스로 날리고 슬랙(Slack) 에 긴급 알람을 뿌려 통신 코어 코드의 자가면역 체계를 완성해 내야 한다. 성능은 한 번 튜닝하고 끝나는 것이 아니라, 평생 수비(Defense) 해야 하는 지표다.
5. 주요 성능 문제 트러블슈팅 사례 연구(Case Study) 및 해결 체크리스트
모든 성능 튜닝과 프로파일링을 치르고도 알 수 없는 먹통 증상에 빠진 현지 엔지니어들을 구제하기 위한, 야전 사령부의 마지막 체크리스트다.
5.0.1 [인스펙션] 최후의 야전 교범 (Field Manual)
문제가 발생했을 때 아무 데나 찍지 말고 이 표를 증상별 순서대로 하나씩 격파하라.
사례 1. “데이터를 많이 보내면 중간에 파이프가 꽉 막힌 듯 10초씩 라우터가 아무 반응도 안 합니다!”
- 원인(Diagnosis): 99% 확률로
TCP Nagle Algorithm충돌이거나Buffer Bloat(소켓 큐가 커널 단에서 무제한 대기) 현상이다. 애플리케이션 수신단(Callback) 이 처리 속도를 못 따라가고 있다. - 해결(Cure):
- 수신단의 콜백 메인 스레드에
sleep()이나 데이터베이스Write대행 로직이 있는지 당장 삭제하라(16.8장). - 커널의 혼잡 제어를
BBR로 교체하며(16.2장), 라우터의 큐를 버리는(Drop-Oldest) 정책을 활성화하라(16.5장).
사례 2. “로보틱스 제어(Ping-Pong) 지연시간이 들쭉날쭉(Jitter) 하고, 0.1초씩 한 번 씩 튑니다.”
- 원인(Diagnosis): 통신망이 무선(Wi-Fi, 5G) 이거나,
Garbage Collection에 맞았을 가능성이 크다. 혹은Interrupt Coalescing에 의해 패킷 처리를 커널이 모으고 있다. - 해결(Cure):
- 이더넷 연결일 경우
ethtool로 인터럽트 병합을 당장 꺼버린다 (rx-usecs 0). (16.2장) - 무선일 경우, 재전송 병목을 막기 위해 무조건 TCP 를 폐기하고
QUIC프로토콜로 전환한다 (16.3장). - 로봇 코드가 파이썬(Python) 이거나 노드(Node.js) 라면 GC 타임로그를 찍고(16.8장), 심각할 경우 코어 모듈만 Rust 나 C++ 바인딩으로 찢어내야 한다.
사례 3. “CPU 0번 코어가 터지려고 하고, Zenoh 라우터가 자꾸 연결을 셀프로 끊습니다.”
- 원인(Diagnosis): 스웜(Swarm) 환경에서 무의미한 다이렉트 그물망 통신(Full-Mesh Peer) 을 걸어놨거나, 너무 넓은 와일드카드(
**) 도배로 O(N) 서칭이 발생하고 있다. - 해결(Cure):
- 라우터를 중앙 집중식
Client-Router스타(Star) 토폴로지로 구조조정한다(16.4장). - 동적 스카우팅(Multicast) 옵션을
false로 꺼버리고 IP를 강제로 박아버린다(16.4장). perf를 돌려 암호화(AES) 부하인지 확인하고, 그렇다면 하드웨어 가속을 켠다(16.7장).