16.5 메시지 처리 및 데이터 페이로드 최적화

16.5 메시지 처리 및 데이터 페이로드 최적화

트럭(네트워크 소켓) 을 아무리 크고 빠르고 튼튼하게 만들어 놔봤자, 그 안에 싣는 화물(Payload) 의 포장 규격이 엉망이면 병목이 온다.

1바이트짜리 온도 데이터를 보낼 때마다 TCP 헤더를 40바이트씩 붙여서 쏘는 바보짓을 하고 있지는 않은가? 아니면 100MB 짜리 영상을 한 통에 우겨 넣으려다 소켓을 터뜨리고 있는가? 이 장에서는 데이터의 질량과 형태에 따라 완벽한 압축과 분할(Fragmentation), 그리고 배칭(Batching) 의 예술을 구사하여 페이로드 트래픽 스펙트럼 자체를 지배하는 런북을 파헤친다.

1. 메시지 일괄 처리(Batching) 파라미터 튜닝을 통한 Throughput 극대화

100원짜리 사탕 1만 개를 해외 직구할 때, 사탕 한 개당 택배 상자(IP/TCP Header, Zenoh Header 등) 를 하나씩 쓴다면 배송비 파산이다. 배칭(Batching) 은 1만 개의 사탕을 가장 큰 택배 상자 하나에 몰아넣어 헤더 오버헤드를 극단적으로 소거하는 핵심 기술이다.

1.0.1 [Runbook] 임계치 배칭(Threshold Batching) 전술

이 세팅은 지연 시간(Latency) 을 일부 희생하여, 처리량(Throughput) 을 무한대로 끌어올린다.

1. 바이트 기준 배칭 (Size-based)
일정 용량이 찰 때까지 라우터는 절대 데이터를 출발시키지 않는다.

// zenohd.json5
transport: {
  link: {
    tx_batch_size: 65535 // 버퍼에 64KB 의 메시지가 찰 때까지 무조건 기다렸다가 압축 발송!
  }
}

2. 시간 기준 배칭 (Time-based Timeout)
그런데 어쩌다 데이터가 안 들어와서 64KB가 영원히 안 차면? 메시지가 출발하지 못한 채 애플리케이션이 멈춰버리는 데드락(Dealock)이 온다. 타임아웃을 같이 걸어 물리적 사형 선고를 내려야 한다.

transport: {
  link: {
    // 64KB 가 안 찼어도, 5밀리초(ms)가 지나면 가차없이 강제 발송!
    tx_batch_timeout: 5 
  }
}

초당 10만 건씩 들어오는 거래소(Finance) 티커 데이터나, 센서 스트림 덤프를 칠 때 이 파라미터들을 결합하면 CPU 점유율을 1/10 로 박살 내면서 초당 처리 건수(MPS) 가 수백만 건으로 치솟는 극한의 스로틀업(Throttle-up) 을 경험할 수 있다.

2. 대용량 페이로드의 효율적인 분할(Fragmentation) 및 재조립(Reassembly) 기법

배칭(Batching) 의 반대 상황이다. 10MB짜리 4K 비디오 프레임이 도착했다. 이 거대한 코끼리를 좁은 소켓 구멍으로 어떻게 안전하게 통과시킬 것인가? OS 레벨(IP 단편화) 에 맡기면 패킷 하나가 사라졌을 때 10MB 전체가 증발해버린다.

2.0.1 [인스펙션] 유저 스페이스 절단기 전술

OS 가 코끼리를 찢어발기기 전에, 어플리케이션(Zenoh) 단에서 아주 깔끔한 규격의 벽돌로 만들어 넘기는 것이다.

1. 지능형 썰기 (Application-Level Fragmentation)
Zenoh 의 프래그멘테이션(Fragmentation) 기능을 켜면, 10MB 의 영상이 들어왔을 때 Zenoh 엔진이 이를 약 6만 바이트 단위(혹은 MTU 단위) 로 자르고 넘버링(Sequence Number) 을 붙여서 보낸다.

  • 장점: 도중에 3번째 조각 하나가 유실되더라도, Zenoh 에서는 3번째 조각만 핀셋 재전송(Retransmission) 요구를 하여 전체 프레임을 복구할 수 있다.

2. 메모리 풀 확보 (Reassembly Buffer)
자른 데이터를 수신단에서 다시 조립하려면 대기실(Buffer) 이 넉넉해야 한다.
로봇이 여러 대의 4K 카메라를 동시에 수신하고 있다면 조립 대기실(RAM) 이 터져나간다. 대용량(수십 MB 단위) 의 데이터를 받을 때는 zenohd.json5rx_buffer_size 리미트를 수백 메가바이트(MB) 로 공격적으로 풀어줘야 한다.

3. 페이로드 직렬화/역직렬화(Serialization/Deserialization) 오버헤드 최소화

Zenoh는 바이트 스트림 프로토콜이다. 개발자가 put(data) 를 칠 때 이 데이터가 바이트(Byte) 배열로 변환되는 과정. 즉, 직렬화(Serialization) 코드가 더러우면 데이터가 네트워크를 타보기도 전에 CPU 가 말라 죽는다.

3.0.1 [Runbook] 제로 오버헤드 포맷팅 런북

1. 절대 금지: JSON 의 저주
JSON 은 눈으로 보기는 예쁘지만, 1을 표현하기 위해 "one": 1 이라고 적어야 한다(텍스트 파싱). 로봇 공학에서 1kHz 로 JSON 을 때려 붓는 다는 것은 자살 행위이자 CPU 디코더 파괴의 주범이다.

2. 이진 바이너리(Binary) 프로토콜 탑재
Protobuf 도 약간 무겁다. 현대 로보틱스 성능의 극한은 오직 CDDL / MessagePack / CDR / FlatBuffers 처럼 메모리 구조와 1:1 로 박히는 이진 포맷뿐이다.

  • ROS2 와 100% 성능 시너지를 낼 때는 CDR (Common Data Representation) 이 표준이다. 로봇의 C++ 메모리 변수 스택이 복사나 변환 연산(Zero-Copy) 없이 그 모양 그대로 네트워크 소켓에 실려 바다를 건너간 뒤, 수신자의 메모리 변수 스택에 1마이크로초도 안 되어 팡! 하고 부활(Cast) 해버리는 최강의 스루풋 파이프다.

4. QoS(Quality of Service) 기반 트래픽 우선순위(Priority) 할당 매커니즘

하나의 좁은 인터넷망 안에, A (충돌 방지 센서 신호) 와 B (어제 찍은 강아지 녹화 영상) 가 동시에 출발선에 섰다.
네트워크 혼잡이 왔을 때, 누구는 살리고 누구는 죽일(Drop) 것인가?

4.0.1 [인스펙션] 생사여탈권(Life and Death) QoS 전술

Zenoh 의 데이터 모델은 억지로 트래픽을 분류하지 않고, 데이터 페이로드(Put) 를 퍼블리싱할 때 개발자가 부여하는 우선순위 태그(Priority) 만으로 우아한 교통정리를 수행한다.

1. Zenoh Priority 태그 부여 (개발자)
초저지연(Control) 에서 최고 스루풋(Data) 까지 지원한다 (1 ~ 7 스케일).

// Rust 퍼블리셔 코드 예시
// 1. 긴급 제동 (최고 우선순위: RealTime)
publisher.put("robot/brake", 1).priority(Priority::RealTime).res().await.unwrap();

// 2. 로그 파일 백업 (최저 우선순위: Background)
publisher.put("robot/log", dump).priority(Priority::Background).res().await.unwrap();

2. 라우터 강제 차선(Priority Queue) 편입 (인프라)
저 선언을 받은 데몬 프로세스(zenohd) 내부에는 각각 7개의 파이프라인(FIFO 큐) 이 물리적으로 격리되어 동작한다.
Background 데이터가 100 메가바이트씩 쏟아져 들어오고 있더라도, RealTime 태그가 달린 1바이트짜리 브레이크 명령이 들어오는 그 즉시, 라우터는 Background 쓰레드를 잠시 동결(Freeze) 시켜버리고 RealTime 부터 OS 소켓 밖으로 빼낸다. 애플리케이션 레벨의 완벽한 교통 신호등 제어다.

5. Congestion Control(혼잡 제어)과 Drop-정책(Drop-Oldest, Drop-Newest) 설정

큐가 터져나간다. 라우터가 도저히 다 처리할 수 없는 수준의 쓰나미가 몰려왔을 때 시스템 전체가 마비(OOM) 되지 않도록, 과감하게 데이터를 “학살(Drop)” 해야만 살아남을 수 있다.

5.0.1 [Runbook] 살육의 논리학 (Queue Eviction)

꽉 찬 소켓 버퍼에 새로운 패킷이 또 들어왔다. 누구를 죽일 것인가?

1. 최신 데이터가 생명인 경우 (Drop Type: LIFO / Drop-Oldest)

  • 대상: 초당 100번씩 들어오는 실시간 기온 센서.
  • 논리: 어차피 최신(지금 이 순간) 의 기온이 가장 중요하다. 1초 전 기온 데이터가 아직 큐에 머물러 있다면, 그 오래된 쓰레기는 큐에서 가차 없이 빼서 찢어버리고(DropOld), 지금 당장 들어온 최신 온도 데이터를 큐에 우겨 넣는다.
  • 가장 파괴적이고, 가장 현대적인 실시간 인더스트리 제어의 핵심 정책이다.

2. 한 조각도 양보할 수 없는 경우 (Drop Type: Drop-Newest)

  • 대상: 로컬 디바이스에 저장해 둔 어제 자 영상 로그 파일 전송 트래픽.
  • 논리: 로그 파일 중간의 1초가 뻥 뚫려 날아가면 파일 전체가 깨진다(Corrupt).
  • 이때는 수신망 큐가 꽉 차 있으면, 무작정 새로 들어오는 최신 패킷들을 다 거부(DropNew, Drop-Newest) 한다. 기존 물량이 소화될 때까지 절대 안 받는다(혼잡 제어 신호로 발신자 측의 퍼블리싱 파이프라인이 멈춘다).

이 정책의 조율에 실패하면, 라우터 데몬은 결국 리눅스의 영문 모를 “OOM Killer(Out-of-Memory Killer)” 당수단에 메모리를 초과했다는 죄명으로 목이 날아가게 된다.