2.5 통신 패턴의 추상화 및 다중 패러다임

2.5 통신 패턴의 추상화 및 다중 패러다임

과거의 통신 미들웨어 생태계는 명확한 이분법적 경계선이 존재했다. 웹 환경의 HTTP나 gRPC는 클라이언트가 능동적으로 데이터를 당겨오는(Pull) 동기식 요청/응답(Request/Reply, RPC) 패턴의 절대 강자였고, MQTT나 DDS는 발생한 이벤트를 비동기적으로 밀어내는(Push) 출판/구독(Publish/Subscribe) 패턴을 지배했다.

결과적으로 산업 현장의 로봇 한 대를 제어하기 위해, 비디오 스트리밍을 위해서는 WebRTC를, 상태 보고를 위해서는 MQTT를, 원격 자율주행 명령 하달을 위해서는 gRPC를 동시에 구동해야 하는 ‘프로토콜의 누더기(Patchwork)’ 아키텍처가 당연시되어 왔다.

**Zenoh(제노)**의 가장 위대한 공학적 성취 중 하나는 이처럼 분리되어 있던 이질적인 통신 패러다임들을 단일한 코어 추상화 레이어(Core Abstraction Layer) 위로 완벽하게 통합(Unification)해 냈다는 점이다. Zenoh 네트워크 안에서는 Pub/Sub, 분산 쿼리(Distributed Query), 그리고 RPC 패턴이 별개의 하위 시스템으로 도는 것이 아니라, 2.4장에서 설명한 **‘키-값(Key-Value) 기반 공간 수학’**이라는 동일한 논리적 뿌리에서 파생되는 수학적 다름에 불과하다.

본 장에서는 Zenoh가 어떻게 단일한 와이어 프로토콜과 라우팅 인프라를 사용하여 비동기 데이터 푸시(Pub/Sub)는 물론, 지연 바인딩(Late Binding)이 가능한 강력한 원격 분산 질의(Pull), 그리고 RPC에 이르기까지 현존하는 모든 분산 통신 패턴을 자유자재로 모핑(Morphing) 해내는지 그 심층적인 메커니즘을 탐구한다.

1. 출판/구독(Publish/Subscribe) 패턴: 비동기 데이터 푸시(Push)

Zenoh(제노)가 지원하는 다중 패러다임 중 가장 원초적이고 기초가 되는 통신 방식은 출판/구독(Publish/Subscribe, Pub/Sub) 패턴이다. 이는 센서의 온도 변화나 자율주행 로봇의 라이다(LiDAR) 스캔 데이터처럼 높은 빈도로 생산되는 데이터 스트림을 생산자(Publisher)에서 소비자(Subscriber)로 지연 없이 밀어내는(Push) 데 최적화된 비동기(Asynchronous) 모델이다.

1.1 토폴로지 독립적인 비동기(Asynchronous) 브로드캐스팅

Zenoh의 Pub/Sub는 MQTT와 유사해 보이지만, 아키텍처의 심연에서는 완벽히 다르게 작동한다. MQTT는 데이터가 반드시 브로커(Broker)라는 단일 경유지를 거쳐야만 하는 ‘중앙 집중식 허브(Hub)’ 의존형 구조이다.
반면 Zenoh는 완전 분산형 메쉬(Mesh)와 트리(Tree) 토폴로지를 기반으로 작동하므로, 생산자와 소비자 사이에 명시적인 브로커가 존재하지 않는다.

어떤 퍼블리셔(Publisher)가 factory/robot/1/telemetry라는 고유한 식별자(Resource Name)를 달고 데이터를 발행하면, 이 데이터 패킷은 사전에 글로벌 트라이(Trie) 라우팅 테이블에 구축되어 있던 최단 경로 트리(SPT)를 타고 마치 강물이 갈라지듯 네트워크 전역의 구독자들에게 즉각적으로 살포(Fan-out)된다.

1.2 블라인드 퍼블리싱 (Blind Publishing)과 관심사(Interest)의 지연 결합

일반적인 TCP 소켓 프로그래밍에서는 발신자가 데이터를 쏘기 전에 수신자의 상태(Liveliness)나 소켓 연결 여부를 끊임없이 확인해야 한다. 하지만 Zenoh의 퍼블리셔는 수학적 의미의 완벽한 **블라인드 퍼블리싱(Blind Publishing)**을 수행한다.

퍼블리셔는 자신이 생산하는 데이터를 현재 누가 1명이라도 듣고 있는지, 혹은 단 한 명의 구독자도 없는지(Dangling) 전혀 신경 쓰지 않는다. 그저 로컬 버퍼에서 네트워크 인터페이스를 향해 데이터를 뱉어낼 뿐이다 (Fire and Forget).

  • 만일 네트워크 전역에 해당 키를 구독(Subscribe)하는 노드가 단 하나도 없다면, 첫 번째 진입점 라우터(Edge Router)에서 포워딩 엔진에 의해 패킷은 조용히 폐기(Drop)되어 불필요한 대역폭 낭비를 원천 차단한다.
  • 반대로 10만 대의 구독자가 대기 중이라면, 라우터 망이 이를 10만 개로 자체 복제(Replication)하여 전달한다.

결과적으로 Zenoh의 데이터 생산자와 소비자는 시간적(Temporal)으로도, 공간적(Spatial)으로도 완전히 분리(Decoupled)되는 이상적인 비동기 모델을 완성한다. 퍼블리셔 장비의 전원 코드와 구독자 장비의 전원 코드를 언제 뽑았다 꽂아도 시스템 전체의 상태 머신(State Machine)은 붕괴하지 않는다.

2. 분산 질의(Distributed Query): 지연 바인딩과 풀(Pull) 기반 데이터 획득

Pub/Sub 패턴은 쉴 새 없이 팽창하는(Flowing) 동적 데이터인 ’Data in Motion’을 제어하는 데는 탁월하지만, 과거의 이력을 묻거나(과거의 센서 평균치), 원격 데이터베이스에 조용히 저장된 ’Data at Rest’를 가져오는 데는 구조적 한계를 지닌다. HTTP GET과 같은 전통적인 점대점(Point-to-Point) 쿼리가 필요하지만, IP를 노출하고 싶지 않은 분산 환경의 딜레마를 타파하기 위해 **Zenoh(제노)**는 세상에 없던 독특한 패러다임인 **분산 질의(Distributed Query)**를 제안한다.

2.1 공간적 풀(Pull) 모델의 재정의

Zenoh의 Query는 내가 어떤 대상(서버 IP)을 찔러서 값을 가져올지 명시하지 않는다. 오직 셀렉터(Selector, 예: factory/sensor/*/temp)라는 논리적 이름 공간만을 던진다.
어떤 클라이언트 노드가 라우터 네트워크 망에 이 와일드카드가 포함된 ‘Query’ 메시지를 투척하면, 라우터 엔진은 이 쿼리를 브로드캐스트하는 것이 아니라 해당 이름 공간표에 등록된 잠재적 응답자(Queryable)들의 보유 경로를 역산하여 스마트하게 멀티캐스트(Targeted Multicast) 포워딩한다.

2.2 응답자(Queryable)와 지연 바인딩 (Late Binding)

시스템의 어떤 노드들은 데이터 퍼블리셔가 아니라 **쿼리어블(Queryable)**이라는 특별한 상태로 대기할 수 있다. 이들은 센서의 현재 캐시 값을 쥐고 있는 단순 피어(Peer)일 수도 있고, 하드디스크 1TB를 보유한 거대한 TimescaleDB 스토리지 백엔드일 수도 있다.

흥미로운 점은, 쿼리를 던지는 쪽과 쿼리를 받아 응답하는 쪽의 연결(Binding)이 물리적인 세션 형성 시점에 결정되지 않고, 쿼리 패킷이 네트워크를 부유하다가 실제 목적지 노드에 도착하는 순간 극적으로 결속되는 지연 바인딩(Late Binding) 구조를 띤다는 것이다.
이를 통해 클라이언트 A는 자신에게 답해주는 데이터 소스가 1미터 앞의 에지 로봇인지, 지구 반대편의 클라우드인지 전혀 알 필요 없이 오직 반환된 데이터(Reply) 그 자체만을 취하게 된다.

2.3 와일드카드와 다중 응답(Multi-Reply)의 비동기성 도래

일반적인 HTTP GET은 요청 하나에 응답(Response) 하나라는 강직한 1:1 대응 원칙을 지킨다. 하지만 Zenoh의 분산 질의는 이 원칙을 산산조각 낸다.
클라이언트 노드가 sensor/** 라는 와일드카드 쿼리를 날렸을 때, 네트워크 상에 존재하는 100대의 쿼리어블 센서들이 동시에 이 질의에 반응(Hit)할 수 있다.
이 경우, 단일 쿼리는 네트워크를 거치며 100갈래로 가지를 치고(Fork), 각 센서가 반환하는 100개의 데이터 덩어리는 독립적인 비동기 스트림(Stream) 형태의 응답(Reply)으로 클라이언트에게 소나기처럼 쏟아져 들어온다. 이것이 바로 단순한 RPC(Remote Procedure Call)와 Zenoh의 분산 쿼리가 궤를 달리하는 결정적 경계선이다.

3. 분산 질의에 대한 다중 응답 병합(Consolidation) 및 스트림(Stream) 처리 전략

와일드카드 쿼리에 의해 여러 대의 응답자(Queryable)가 비동기적으로 데이터를 쏟아낼 때, 클라이언트 입장에서 가장 곤혹스러운 문제는 **“이 수많은 응답을 어떻게 효율적으로 수신하고 처리할 것인가?”**이다.
수천 개의 센서가 거의 동시에 패킷을 반환한다면, 클라이언트 노드의 메모리나 로컬 큐(Queue)는 순간적으로 터져버릴(Overflow) 수 있다. **Zenoh(제노)**는 이러한 다중 응답 스트림을 우아하게 제어하기 위한 병합(Consolidation) 및 하위 호환 스트림 파이프라인을 제공한다.

3.1 응답 스트림의 병합 (Consolidation) 매커니즘

Zenoh 라우터는 단순히 쿼리 응답(Reply) 패킷을 전달하는 우체부가 아니라, 트래픽을 지연 및 결합할 수 있는 능동적인 미들박스(Middlebox) 역할을 수행한다.
클라이언트가 temperature/** 쿼리를 날릴 때 옵션으로 병합(Consolidation) 플래그를 활성화하면, 엣지 라우터들은 각 로컬 센서들로부터 올라오는 개별 응답 패킷들을 즉시 클라이언트에게 쏘지 않는다. 대신 라우터는 아주 짧은 윈도우 갭(Window Gap) 동안 패킷을 버퍼링(Buffering)하면서, 서로 같은 키나 인접한 키를 가진 작은 페이로드들을 하나의 큰 배열(Array) 형태의 메타-패킷으로 뭉쳐버린다(Batching/Consolidation).
결과적으로 클라이언트 네트워크 인터페이스에는 1만 개의 작은 인터럽트(Interrupt) 대신 수백 개의 굵직한 통합 데이터 덩어리가 도착하며, 이는 CPU 처리 오버헤드를 비약적으로 감소시킨다.

3.2 스트리밍 API를 통한 비동기 처리 파이프라인

Zenoh 클라이언트 API(Rust, C++, Python 등) 레벨에서, 분산 쿼리의 반환값은 단일 객체나 단순한 배열이 아니다. 통상적으로 데이터가 끝없이 흘러들어올 수 있는 비동기 스트림(Asynchronous Stream) 혹은 채널(Channel) 형태로 제공된다.

// Zenoh Rust API 기반 분산 쿼리 스트림 처리 개념도 (가상 코드)
let mut replies = session.get("factory/sensor/**").await.unwrap();

while let Some(reply) = replies.next().await {
    match reply.sample {
        Ok(sample) => {
            println!("수신한 데이터 키: {}, 값: {:?}", sample.key_expr.as_str(), sample.value);
            // 수신 즉시 파이프라인 후처리 진행
        },
        Err(err) => println!("응답 처리 오류 발생"),
    }
}

클라이언트는 replies 전체가 무거운 배열로 메모리에 적재되기를 기다릴 필요가 없다. 첫 번째 센서의 응답이 도착하는 즉시 루프(Loop)가 해제되며, 연속적으로 흘러들어오는 데이터 스트림 하나하나를 실시간으로 파생 파이프라인(Map-Reduce 모델 등)에 밀어 넣을 수 있다. 이는 Zenoh가 단순한 통신망을 넘어, 그 자체로 분산 스트림 처리 엔진(Distributed Stream Processing Engine)의 프론트엔드 역할을 수행할 수 있음을 증명한다.

4. Liveliness 토큰 아키텍처: 시스템 상태 모니터링 및 결함 감지

분산 시스템에서 가장 구현하기 까다롭지만 필수적인 기능이 바로 네트워크에 연결된 장비들의 ’생사 여부(Alive/Dead)를 실시간으로 모니터링’하는 것이다. 로봇이 오프라인 상태가 되었거나, 드론과 통제국 사이의 연결이 끊긴(Connection Lost) 상황을 시스템 전역에서 신속히 알아차리지 못하면 심각한 안전사고나 데이터 정합성 파괴로 이어진다.

전통적인 시스템에서는 각 노드가 중앙 서버에 주기적으로 ’나 살아있다’는 핑(Ping)을 쏘는 폴링(Polling) 방식이나 무거운 하트비트(Heartbeat) 메커니즘을 썼지만, **Zenoh(제노)**는 완전히 탈중앙화된 상태 감지 메커니즘인 라이블리니스 토큰(Liveliness Token) 아키텍처를 도입하여 오버헤드 없이 직관적인 결함 감지 기능을 제공한다.

4.1 라이블리니스 토큰(Liveliness Token)의 생성과 소멸 원리

Zenoh 네트워크의 애플리케이션(Peer 또는 Client)은 시작될 때 특정 URI(Resource Name) 공간에 자신의 생존 증명서인 라이블리니스 토큰을 ‘붙인다(Declare)’.
예를 들어 1번 드론이 이륙하면 시스템에 drone/1/liveliness라는 토큰을 선언하는 식이다.

이 토큰은 일반적인 데이터와 다르다. 이 토큰은 드론 측 세션(Session) 객체와 물리적인 UDP/TCP 와이어 연결의 생명주기(Lifecycle)에 하드 코딩되어 묶여있다. 만약 1번 드론에 장착된 컴퓨터의 전원이 나가버리거나 배터리가 방전되어 물리적 소켓 연결이 비정상적으로 끊어지면, Zenoh 에지 라우터는 소켓 드롭(Socket Drop) 이벤트를 감지한 즉시 drone/1/liveliness 토큰을 강제 소멸(Drop)시켜버린다.

4.2 라이블리니스 토큰의 구독(Subscribe)과 결함 처리 파이프라인

관제 시스템(GCS)은 드론들에게 ’살아있냐’고 물어볼 필요가 없다. 그저 와일드카드를 사용하여 drone/*/liveliness라는 토큰 공간 자체를 영구 구독(Subscribe) 해두면 그만이다.

  • 특정 드론이 새롭게 접속하면 관제 시스템으로 [Alive]라는 토큰 생성 알람이 푸시(Push)된다.
  • 특정 드론과 라우터 간 물리적 통신이 단절되면, 라우터 망이 자체 판단하여 관제 시스템으로 [Dropped]라는 토큰 소멸 알람을 즉각 푸시한다.

결과적으로 개발자는 타임아웃 주기를 관리하거나 데드락(Deadlock)에 빠진 스레드를 폴링하는 무거운 코드를 짤 필요가 없다. 토큰이 끊겼다는 이벤트 콜백(Callback)이 떨어지면, 즉시 드론에 대한 구조 시퀀스를 발동시키거나 라우팅 테이블에서 해당 장비를 폐기하는 결함 대응(Fault-Tolerance) 파이프라인을 가동하면 되는 것이다. 이는 Zenoh가 물리 계층의 오류(Physical Link Failure)를 논리적인 상태 데이터(Logical State Data)로 가장 아름답게 추상화한 대표적 사례이다.

5. 원격 프로시저 호출(RPC) 및 분산 연산 파이프라인 연계 방안

상태를 푸시하는 Pub/Sub과 데이터를 당겨오는 Distributed Query의 훌륭한 추상화에도 불구하고, 때로는 분산 시스템에서 전통적인 **명령 기반 제어(Command-and-Control)**가 필수적인 순간이 찾아온다. 로봇팔에 “X, Y 좌표로 지금 당장 이동해“라는 제어 명령을 내리고, 로봇이 실제로 이동을 조향 한 뒤 “이동 완료, 모터 온도 정상“이라는 계산된 결괏값을 회신받아야 하는 동기식 상호작용이 바로 그것이다.

Zenoh(제노) 플랫폼에서 **원격 프로시저 호출(Remote Procedure Call, RPC)**은 별도의 전용 프로토콜(예: gRPC)을 차용하지 않고, 앞서 다룬 ’분산 쿼리(Distributed Query)’의 응답자(Queryable) 메커니즘을 교묘하게 변형하여 구현해 낸 극한의 다형성(Polymorphism)을 과시한다.

5.1 Queryable을 활용한 양방향 파이프라인 (Two-way Pipeline) 확립

보통 쿼리를 던질 때는 ? 파라미터만 던지고 응답을 받는다. 하지만 Zenoh의 Query는 파라미터를 넘어 페이로드(Payload) 자체를 질의에 태워 보낼 수 있다.
이를 통해 원격 함수의 인자(Arguments)를 페이로드에 직렬화(Serialize)하여 쏠 수 있다.

  1. RPC 서버 구축: 로봇 제어 프로세스(응답자)는 robot/arm/move라는 이름 공간(URI)을 점유하여 **쿼리어블(Queryable)**로 선언하고 대기한다. 일반적인 DB 스위칭이 아니라, 메모리상에서 콜백 함수(Callback Function)를 바인딩해 두는 것이다.
  2. RPC 클라이언트 호출: 명령 서버는 robot/arm/move URI에 대고 쿼리를 쏘면서 목적지 좌표 {"x": 10, "y": 20}라는 제어 페이로드를 동봉한다.
  3. 수행 및 회신: 라우터를 거쳐 페이로드가 담긴 쿼리를 수신한 로봇 제어 프로세스는 즉각 콜백을 실행하여 모터를 구동시킨다. 행위가 끝나면 함수가 완료 상태(Result)를 쿼리의 ‘응답(Reply)’ 패킷으로 포장해 서버로 반환(Return)한다.

5.2 블로킹(Blocking) 없는 비동기 RPC 체인 (Async RPC Chain)

기존 gRPC의 블로킹 방식과 달리, Zenoh의 RPC는 태생적으로 와이어 상에서 비동기 스트림 채널을 유지한다.
따라서 로봇이 팔을 1분간 움직이는 통안 클라이언트(명령 서버)의 쓰레드는 동기화(Sync) 락(Lock)에 걸린 채 멈춰 서 있지 않는다. Zenoh의 RPC 호출(Get)은 비동기 퓨처(Future) 혹은 스트림 객체를 반환해 주며, 클라이언트는 언제든지 메인 사이클을 돌다가 결과가 반환되었을 때만 후처리를 수행할 수 있다.

더 나아가, 2.5.2장의 다중 응답 특성을 결합하면 robot/*/move라는 단 한 줄의 RPC 호출만으로 공장 내의 로봇 100대에게 이동 명령을 일제 점화(Multicast)하고, 완료된 로봇부터 순차적으로 떨어지는 100개의 비동기 함수 결괏값을 스트림으로 수거하는 마법 같은 ’1 대 N 분산 RPC 매커니즘’이 터무니없을 만큼 쉽게 완성된다.