2.6 Zenoh 와이어 프로토콜(Wire Protocol) 및 프레이밍(Framing)
지금까지 Zenoh(제노)가 제공하는 고차원적인 데이터 모델과 라우팅, 그리고 추상화된 통신 패러다임을 살펴보았다. 하지만 이 모든 화려한 분산 시스템의 마법도 결국 물리적인 네트워크 케이블이나 무선 전파를 타고 흐르는 0과 1의 비트 스트림(Bit Stream)으로 변환되어야만 실체가 된다.
어떤 미들웨어가 1초에 수백만 건의 데이터를 처리할 수 있는지, 혹은 동전 크기만 한 마이크로컨트롤러(MCU)에서 구동될 수 있는지의 여부는 궁극적으로 그 미들웨어가 채택한 **와이어 프로토콜(Wire Protocol)**의 구조적 정밀도에 달려있다. JSON이나 XML 기반의 텍스트 프로토콜이 가독성을 이유로 방대한 바이트를 낭비할 때, 자원 제약적 엣지 컴퓨팅을 정조준한 Zenoh는 단 1바이트의 오버헤드를 깎아내기 위해 뼈 아픈 최적화 과정을 거친 이진(Binary) 프로토콜을 탄생시켰다.
본 장에서는 Zenoh 통신 엔진의 가장 깊숙하고 은밀한 심연, 즉 메모리 상의 객체가 어떻게 바이트 배열로 직렬화(Serialization)되고 단위 프레임(Frame)으로 쪼개져 물리적 네트워크 소켓으로 투입되는지를 탐구한다. 최소 오버헤드를 달성하기 위한 와이어 포맷 구조부터, 거대한 페이로드를 처리하기 위한 단편화(Fragmentation) 및 재조립 기법, 그리고 대역폭 한계를 돌파하기 위한 배치(Batching) 전략까지, 선형적 시간과 공간을 극복하려는 Zenoh 와이어 프로토콜의 극한의 엔지니어링 묘미를 분석할 것이다.
1. 최소 오버헤드를 위한 와이어 포맷 구조의 이해 (바이트 단위 분석)
클라우드 데이터센터 내부를 흐르는 트래픽은 수 바이트의 오버헤드쯤은 거뜬히 무시할 수 있는 넉넉한 대역폭을 누린다. 하지만 Zenoh(제노)의 주 무대는 로라(LoRa)망을 타는 원격 센서, 좁은 대역폭으로 통신하는 군집 드론, 대당 가용 램(RAM)이 킬로바이트(KB) 단위에 불과한 8비트 마이크로컨트롤러(MCU) 환경이다.
이 척박한 세계에서 프로토콜 헤더(Header)의 비만도는 곧바로 시스템의 지연(Latency)과 배터리 고갈로 직결된다.
따라서 Zenoh의 와이어 포맷(Wire Format)은 비트 팩킹(Bit-packing) 기술과 가변 길이 정수(Variable-length Integer) 인코딩을 극한으로 끌어올려 설계되었다.
1.1 프레임의 기본 골격 (Frame Anatomy)
네트워크 소켓(TCP/UDP)을 통해 전송되는 Zenoh의 가장 작은 단위(Unit)를 **메시지(Message)**라고 부른다.
하나의 Zenoh 메시지는 아주 단순화하면 세 가지 구역으로 나뉜다.
- 헤더 (Header - 통상 1~2 Bytes): 메시지의 본질(예: 이건 Pub 데이터인가? Query 요청인가? Liveliness 토큰인가?)을 규명하는 고정 식별자 구역이다.
- 가변 길이 옵션 구역 (Options): 데이터가 커서 단편화(Fragmentation)가 발생했는지, 라우팅 홉(Hop) 카운트가 몇 번인지, 타임스탬프가 존재하는지 등을 알리는 부가 조항들이 오직 ‘필요할 때만’ 가변적으로 들러붙는다.
- 페이로드 (Payload): 메시지의 핵심인 실제 데이터(Data) 바이트 배열이 담기는 구역이다.
1.2 5바이트의 기적 (The 5-Byte Magic)
가장 흔하게 발생하는 통신인 ‘Pub(데이터 발행)’ 메시지를 해부해 보자.
만약 ROS 기반 로봇이 조향 각도 “15“라는 페이로드(1 byte)를 robot/steer라는 키(Key)에 실어 보낸다고 할 때, DDS 같은 전통적인 미들웨어는 세션 ID, QoS 설정, RTPS 헤더 등등을 덕지덕지 붙여 순식간에 40~50바이트의 오버헤드를 만들어낸다.
하지만 Zenoh의 데이터 푸시(Data Message) 프레임의 최소 오버헤드는 단 5바이트(Bytes)이다.
- 메시지 식별용 헤더 (1 byte)
- 키 길이 정보 (1 byte, 혹은 사전에 라우터와 합의된 압축 ID로 치환 시 1~2 bytes)
- 데이터 길이 정보 (VLE, 1 byte)
- 부가 옵션 플래그 패킹
이 믿을 수 없는 다이어트 덕분에, Zenoh는 MTU(Maximum Transmission Unit) 파편화 없이 한 틱의 이더넷 프레임 안에 수백 개의 센서 데이터를 구겨 넣을 수 있으며, 통신 모듈의 전력 소모를 비약적으로 절감시킨다.
1.3 VLE(Variable Length Encoding) 기법 적용
Zenoh 와이어 포맷이 극단적 경량화를 이룬 또 다른 비결은 데이터 길이나 식별자 해시값을 표현할 때 고정된 4바이트(32bit) 정수형을 쓰지 않고 **VLE (Variable Length Encoding)**를 쓴다는 점이다.
값이 127보다 작으면 1바이트만 사용하여 온전히 길이를 표현하고, 값이 커질 때만 비트 플래그를 올려 2바이트, 3바이트로 동적 확장한다. 이는 네트워크 패킷 안에 쓸데없이 0으로 꽉 찬 공백(Padding) 바이트가 존재하지 않음을 보장하는 Zenoh의 집요한 공학적 승리이다.
2. 메시지 단편화(Fragmentation)와 재조립(Reassembly) 메커니즘
Zenoh(제노)의 프로토콜 오버헤드가 단 5바이트 수준으로 극한의 가벼움을 자랑한다 할지라도, 사용자가 던지는 페이로드(Payload)의 크기는 예측할 수 없다. 센서의 온도값처럼 1바이트짜리 데이터도 있지만, 수십 메가바이트(MB)에 달하는 펌웨어 업데이트 파일이나 4K 해상도의 라이다(LiDAR) 포인트 클라우드 스트림이 한 번의 Pub 호출로 전달될 수도 있다.
네트워크 인터페이스 카드(NIC)와 물리적인 와이어 계층(Ethernet, Wi-Fi 등)은 한 번에 전송할 수 있는 패킷의 최대 크기, 이른바 MTU(Maximum Transmission Unit) 제한을 지닌다 (통상 이더넷 기준 1500 Bytes 내외).
만일 수십 MB의 페이로드를 통째로 UDP 소켓에 밀어 넣으려 한다면, OS 커널 단에서 패킷이 드롭(Drop)되거나 심각한 파편화 오류가 발생할 것이다. 이를 방어하기 위해 Zenoh는 자체적으로 우아한 단편화(Fragmentation) 및 재조립(Reassembly) 레이어를 와이어 프로토콜 안에 내장하고 있다.
2.1 선제적 단편화 (Proactive Fragmentation)
Zenoh 에이전트(클라이언트나 피어) 내의 전송 엔진은 하위 네트워크 계층의 허용 MTU 크기를 사전에 인지하고, 애플리케이션으로부터 들어오는 대용량 페이로드를 와이어(Wire)에 태우기 직전, 안전한 크기의 **단편(Fragment)**들로 정육점의 고기처럼 균일하게 썰어버린다.
- 각 조각난 단편들 앞에는 해당 패킷이 ’거대한 원본의 일부’임을 알리는
Frag플래그 헤더가 붙는다. - 또한 이 조각이 전체 퍼즐의 몇 번째 조각인지 식별하는 연속성 인덱스(Sequence ID/Offset) 정보가 VLE(가변 길이) 방식으로 첨부되어 후속 조립을 대비한다.
2.2 수신 측 재조립 (Reassembly) 및 제로카피(Zero-copy)
네트워크를 건너 수신 측 라우터나 구독자 노드에 이 파편들이 도착하면, Zenoh의 수신 엔진은 이들을 즉각 사용자 애플리케이션 계층으로 올리지 않고 재조립 버퍼(Reassembly Buffer) 공간에 모아둔다.
Zenoh가 Rust의 동시성(Concurrency)과 메모리 소유권 모델을 극한으로 활용하는 지점이 바로 여기다.
수신 엔진은 들어온 파편들을 메모리 내의 새로운 커다란 배열 공간으로 일일이 다시 복사(Copy)하며 재조립하는 멍청한 짓을 하지 않는다. 대신, 각 파편이 저장된 메모리 블록들의 시작 포인터와 길이 정보만을 연결 고리(Linked-List)나 버퍼 뷰(Buffer View) 형태로 조작하여 가상의 단일한 연속 데이터 덩어리처럼 환영(Illusion)을 만들어 낸다.
이러한 제로-카피(Zero-copy) 기반의 단편화/재조립 아키텍처 덕분에, Zenoh는 거대한 카메라 비디오 스트림을 쪼개고 합치는 과정에서도 CPU 연산과 메모리 덤프 오버헤드를 제로(0) 수준에 가깝게 방어해 낼 수 있다.
3. 배치(Batching) 처리를 통한 네트워크 효율 극대화 방안
2.6.2장에서 하나의 거대한 데이터를 네트워크 허용치에 맞게 쪼개는 단편화(Fragmentation)를 다루었다면, 배치(Batching) 처리는 그 대척점(Antipodes)에 있는 최적화 기법이다.
만약 초당 1만 번씩 발생하는, 고작 1바이트짜리 온도 변화 이벤트를 상상해 보자. 센서가 1바이트의 데이터를 쏠 때마다 TCP/UDP 헤더 수십 바이트와 IP 프레임, 이더넷 프레임을 덕지덕지 붙여 1만 번을 흩뿌린다면(System Call 발동), 이는 데이터를 보내는 연산보다 택배 상자를 포장하고 운송장 스티커를 붙이는 행위에 시스템 대역폭과 CPU 자원의 99%를 낭비하는 꼴이 된다.
이러한 소형 다빈도 트래픽의 모래알 폭풍을 제어하기 위해 Zenoh(제노) 와이어 프로토콜은 **메시지 배치(Message Batching)**라는 거대한 삽을 장착하고 있다.
3.1 논리적 메시지의 밀착 결합 (Piggybacking)
Zenoh의 전송 엔진 내부에는 발송을 대기하는 큐(Tx Queue) 공간이 존재한다.
애플리케이션 계층에서 put(데이터 A), put(데이터 B), put(데이터 C)가 연진발 적으로 쏟아져 내릴 때, Zenoh 엔진은 이를 즉시 물리적 와이어(Wire)로 밀어 넣지 않는다.
대신, 아주 찰나의 시간(마이크로초 단위의 윈도우)을 대기하며 이 독립적인 세 개의 메시지를 단일한 네트워크 패킷 커버(UDP Datagram 혹은 TCP 세션 블록) 안에 짐을 싸듯 우겨 넣는다.
A, B, C 메시지는 각자의 5바이트 남짓한 극소형 본래 헤더만을 간직한 채, 물리 계층에서는 ’하나의 거대한 프레임’으로 뭉쳐져(Batched) 네트워크 바다를 건넌다.
3.2 배치 처리의 극적 효용
수신 측 라우터가 배치가 적용된 덩어리 패킷을 받게 되면, 프레이밍 엔진(Framing Engine)은 바이트 배열을 흝어내려가며 “A 메시지 끝, 여기서부터 B 메시지 시작, 여기서부터 C 메시지 시작“을 정확히 슬라이싱(Slicing)해 낸다.
- 네트워크 오버헤드 소멸: UDP/IP 헤더가 1만 번 붙을 것이 단 1번으로 획기적으로 압축된다.
- 시스템 콜(System Call) 최소화: OS 커널 단을 1만 번 괴롭히며 컨텍스트 스위칭(Context Switching)을 유발하던
send()호출이 수십 번 수준으로 급감하여 서버의 짐을 크게 덜어준다.
이처럼 자원 제약적 환경을 겨냥한 단골 무기인 초절전 비트 패킹 헤더 설계(2.6.1), 거대 구조물을 옮기기 위한 단편화 제로카피 기술(2.6.2), 그리고 먼지를 뭉쳐 진흙 덩이로 날리는 배치(Batching) 전략이 유기적으로 결합된 결정체가 바로, 어떤 조건 속에서도 초고속 도로를 개척해 내는 Zenoh 와이어 프로토콜의 진면목이다.
4. 선언적 직렬화(Declarative Serialization) 및 커스텀 인코딩(Custom Encoding) 최적화
데이터가 네트워크 와이어(Wire)를 타기 위해서는 메모리 상에 존재하는 복잡한 객체(Object) 구조를 선형적인 1차원 바이트 배열로 풀어내는 직렬화(Serialization) 과정이 필수적이다.
ROS 1 패밀리는 고유의 CDR(Common Data Representation) 변형을 강제했고, 최신 웹 마이크로서비스들은 JSON이나 Protobuf에 의존한다. 하지만 진정한 의미의 다중 패러다임 미들웨어를 지향하는 **Zenoh(제노)**는 특정 직렬화 포맷을 프레임워크 단에서 강제하는 오만함을 버렸다. 대신, 페이로드(Payload)를 완전한 블랙박스(Opaque) 원시 바이트스트림으로 취급하되, 개발자가 자신의 도메인에 가장 적합한 방식을 자유롭게 가져다 붙일 수 있는 선언적 직렬화(Declarative Serialization) 아키텍처를 제공한다.
4.1 페이로드의 Opaque(불투명) 속성과 메타데이터 분리
Zenoh 프로토콜 엔진의 시각에서, 사용자가 밀어 넣은 데이터는 그저 길이를 알 수 없는 [u8] (Unsigned 8-bit Integer Array) 덩어리에 불과하다. Zenoh 라우터는 이 본문을 파싱(Parsing)하거나 해석하려는 시도를 전혀 하지 않는다.
대신, 직렬화된 데이터가 어떤 형식인지 꼬리표를 달아주는 역할은 철저히 2.4.5장에서 살펴본 인코딩(Encoding) 메타데이터 헤더에 위임된다.
- 퍼블리셔(Publisher): “내가 지금 쏘는 이 바이트 뭉치는 CBOR(Concise Binary Object Representation) 규칙으로 압축한 거야“라고 헤더에 명시한다.
- 라우터(Router): 내용물은 무시한 채, 헤더와 목적지 식별자만 보고 빛의 속도로 포워딩(Forwarding)한다.
- 구독자(Subscriber): 헤더를 먼저 읽고, 자신의 로컬 애플리케이션에 탑재된 CBOR 디코더(Decoder)를 호출하여 원본 객체로 복원한다.
4.2 Zero-overhead 커스텀 인코딩 지원
이러한 유연성 덕분에, 엔지니어는 Zenoh 환경에서 통신 오버헤드를 극단적으로 낮추기 위해 자신만의 커스텀 인코딩(Custom Encoding) 기법을 손쉽게 적용할 수 있다.
특수한 군사용 드론의 짐벌(Gimbal) 각도(Roll, Pitch, Yaw)를 전송한다고 가정해보자. 세 개의 16비트 부동소수점(Float16) 값을 JSON으로 변환하면 {"r":1.2,"p":0.5,"y":0.1} 형태로 수십 바이트가 낭비된다.
하지만 개발자는 애플리케이션 레벨에서 단 6바이트짜리 촘촘한 C-Struct 구조체 팩킹을 수행한 뒤, Zenoh의 인코딩 헤더를 application/custom-gimbal로 명시하여 쏴버릴 수 있다.
Zenoh의 하단 와이어 프레이밍 엔진은 이 커스텀 바이트를 5바이트의 기본 헤더만 씌운 채 네트워크로 송출한다. 결과적으로 JSON 대비 네트워크 소모량을 1/10 수준으로 절감하면서도 프로토콜의 표준(Standard)은 한 치도 훼손되지 않는다.
이는 리소스가 극도로 제한된 마이크로컨트롤러(MCU) 프로그래밍에서 판을 뒤집는(Game-changer) 강력한 최적화 무기가 된다.
5. 프로토콜 버전 호환성(Compatibility) 유지 전략
새로운 기능이 추가되고 버그가 수정되면서 미들웨어의 코어 엔진은 끊임없이 진화한다. 그러나 수명이 10년에 달하는 산업용 로봇에 설치된 미들웨어와, 매일 업데이트되는 클라우드 서버의 미들웨어 사이에는 필연적으로 ’버전의 파편화’가 발생한다.
와이어 프로토콜(Wire Protocol) 레벨에서 통신 규격이 한 뼘이라도 어긋나게 되면, 전체 분산 시스템은 데이터를 주고받지 못하고 파국을 맞이하게 된다.
**Zenoh(제노)**는 초창기 설계 단계부터 파괴적인 변경(Breaking Changes)을 방어하고, 구형 엣지 디바이스와 신형 라우터망이 조화롭게 공존할 수 있도록 **프로토콜 버전 호환성(Compatibility)**을 핵심 철학으로 삼아 와이어 포맷 구조를 견고하게 다졌다.
5.1 초기 세션 협상에서의 프로토콜 버전 Handshake
Zenoh 노드들(피어, 클라이언트, 라우터)이 네트워크상에서 처음 조우하여 TCP 소켓 연결을 맺거나 UDP 세션을 초기화할 때, 그들이 가장 먼저 주고받는 패킷은 데이터가 아닌 Init 메시지이다.
이 Init 교환 과정(Handshake)은 상호 간섭 없는 프로토콜 버전 검증기 역할을 수행한다.
Node A: “나는 Zenoh 와이어 프로토콜 버전 0.10.x 를 사용해.”Node B: “나는 버전 1.0.x 를 사용해. 통신 규격을 호환 가능한 최소 공통 분모(Lowest Common Denominator)로 하향 조정(Downgrade)하여 연결하자.”
이처럼 명시적인 버전 협상을 통해, 신형 라우터는 구형 펌웨어를 탑재한 센서가 던지는 구형 마크업 데이터를 정상적으로 파싱 해낼 수 있는 아키텍처 탄력성을 지닌다.
5.2 프로토콜 확장을 위한 예약 공간(Reserved Bytes)과 무시 규칙(Ignore Rule)
과거 수많은 미들웨어들이 버전 업데이트 시 헤더(Header) 구조가 바뀌면서 하위 호환성을 잃곤 했다. Zenoh는 이를 방지하기 위해 2.6.1장에서 언급한 프레임 헤더 속에 ‘가변 길이 옵션(Options)’ 설계라는 영리한 보험을 들어두었다.
미래의 Zenoh 버전에 새로운 암호화 플래그나 특수 QoS 식별자가 추가되어야 한다면, 기존 헤더의 논리적 뼈대를 허무는 대신 이 가변 옵션 구역에 새로운 ID 값으로(Tag-Length-Value 방식) 슬쩍 끼워 넣기만 하면 된다.
만일 구형 Zenoh 노드가 자신이 해석할 수 없는 낯선 ID의 옵션 블록을 수신했다면, 에러를 내뿜고 패킷을 드롭하는 것이 아니라 해당 블록의 길이(Length) 만큼 메모리 포인터를 가볍게 점프(Skip)해버리고 본문(Payload)을 읽어 들이는 **안전 무시 규칙(Safe Ignore Rule)**이 코어 엔진에 내장되어 있다.
이러한 전방 호환성(Forward Compatibility) 방어기제 덕분에, 거대한 인프라를 일거에 셧다운하고 전체 업데이트를 돌려야 하는 재앙 수준의 마이그레이션(Migration) 없이 점진적인 생태계 업그레이드가 가능해진다.