20.6.1 ROS2 (rmw_zenoh) 환경 특화 트러블슈팅

20.6.1 ROS2 (rmw_zenoh) 환경 특화 트러블슈팅

ROS2(Humble 이상) 통신 미들웨어를 DDS에서 rmw_zenoh로 교체하는 순간, 로컬망에 갇혀 있던 로봇 데이터는 인터넷을 타고 전 세계로 브로드캐스트된다. 그러나 브릿지를 건너는 데이터는 원래의 ROS2 네임스페이스 규칙이나 QoS 정책 등 복잡한 제약 조건들을 지니고 있다.

DDS에서 Zenoh로 옷을 갈아입는 과정에서, 어떤 토픽은 옷이 맞지 않아 아예 버려지거나(Message Drop), 어떤 토픽은 너무 크고 지연되어 변환기(Bridge) 자체의 메모리를 박살 낸다. 이 섹션에서는 로봇 공학자들이 실무에서 가장 흔하게 마주치는 세 가지 ROS2 브릿지 붕괴 시나리오를 해부한다.

1. ROS2 네임스페이스 및 토픽 매핑 누락 문제

ROS2의 ros2 topic list 커맨드를 쳤을 때는 분명 /robot1/odom이 출력되는데, 클라우드에 연결된 관제 데스크톱(TypeScript)에서는 z_get으로 아무리 조회해도 데이터가 비어 있는 상황이다. 이는 대부분 도메인(Domain) ID 격리나 브릿지 스코프(Scope) 라우팅 설정이 충돌하여 공중 분해되었기 때문이다.

1.0.1 단계: ROS_DOMAIN_ID 충돌 격리

DDS는 원래 ROS_DOMAIN_ID 변수로 하위 로컬망 내 통신 공간을 격리한다.
그런데 zenoh-bridge-dds를 여러 로봇에서 띄웠다면 어떨까? 로봇 A(도메인 1)와 로봇 B(도메인 2)가 각각 브릿지를 통해 Zenoh 망으로 데이터를 올렸다. 만약 브릿지의 디폴트 설정이 두 도메인 아이디를 구분하지 않고 뭉개어 단일 Zenoh 라우터로 올려버렸다면, 클라우드에서는 A 로봇의 좌표인지 B 로봇의 좌표인지 구분하지 못하고 덮어씌워지는(Overwrite) 파국이 벌어진다.
해법: 브릿지 기동 시 반드시 --scope 파라미터를 사용해 네임스페이스 앞단을 강제로 격리 매핑하라.

## 로봇 1번 브릿지
zenoh-bridge-dds -d 1 --scope "/fleet/robot1"

## 로봇 2번 브릿지
zenoh-bridge-dds -d 2 --scope "/fleet/robot2"

1.0.2 단계: Allow/Deny 정규식 라우팅 필터 누락

관제 서버에서 10메가바이트 크기의 라이다(LiDAR) 스캔 맵을 요청조차 한 적 없는데, ROS2 브릿지는 로컬에서 도는 라이다 토픽까지 무식하게 Zenoh 글로벌 라우터로 쏘아 올리고 있을 수 있다. 이렇게 되면 대역폭이 순식간에 동나서 정작 중요한 cmd_vel 제어 명령이 누락된다.
해법: 모든 브릿지 데몬에는 반드시 정책(Allow/Deny) 리스트를 씌워야 한다.

// zenoh-bridge-dds.json5
{
  // 라우터로 전송할 토픽을 허용(Allow)하거나 차단(Deny)하는 리스트
  allow: [
    ".*/odom",
    ".*/cmd_vel"
  ],
  deny: [
    ".*/camera/.*",
    ".*/scan"
  ]
}

브릿지의 라우팅 설정이 엄밀하지 않으면, 데이터 파이프라인은 로컬에서 돌아야 할 잔여물 트래픽까지 집어삼키는 블랙홀이 되어 버림을 명심하라.

2. QoS(Quality of Service) 설정 불일치로 인한 메시지 유실

ROS2는 DDS의 유산인 방대한 QoS 프로필(Reliability, Durability, History 등)을 사용한다. Zenoh 역시 내부에 상응하는 QoS 정책을 갖췄지만 똑같지는 않다. 이 둘이 브릿지에서 만날 때 톱니바퀴가 엇갈리면 메시지는 조용히 폐기된다.

2.0.1 첫째, Reliability (Best Effort vs Reliable) 교환 실패

로봇 측 퍼블리셔 프로세스가 /camera/image_raw 영상 토픽을 ’Best Effort (신뢰성 없음, 속도 최우선)’로 로컬에 브로드캐스트하고 있다. 그런데 클라우드 관제 대시보드 측에서 Zenoh 브릿지를 거쳐 이것을 ‘Reliable (신뢰성 보장, 재전송 요구)’ 모드로 열어 구독(Subscribe)하려 한다고 치자.
DDS 스펙 및 브릿지 중재 원칙 상, 엄격한 요구 조건(Reliable)을 건 구독자는 느슨한 조건(Best Effort)을 제공하는 발행자와 아예 세션조차 매칭(Matching) 되지 않는다. 토픽 리스트에는 보이는데 구독 시 콜백이 영원히 들어오지 않는 전형적인 증상이다. 양쪽의 Reliability 프로파일을 코드 단에서 강제로 맞춰주어라.

2.0.2 둘째, Durability (영속성) 및 Late-Joiner 데이터 유실

로봇이 구동되자마자 단 한 번 Transient Local 모드로 자신의 하드웨어 스펙(/robot_description)을 퍼블리시하였다. 뒤늦게 클라우드 대시보드가 구동되어 Volatile 모드로 이를 구독하려 하면, 이전 메시지는 과거로 묻힌 채 전달되지 않는다(Late-joiner problem).

zenoh-bridge-dds는 내부적으로 이 QoS 프로파일을 릴레이하지만 완벽한 과거 데이터 마법상자가 아니다. 영속성이 필요한 초기화(Initialization) 토픽은 Zenoh 라우터 단에서 앞서 다룬 storage_manager 스토리지 플러그인과 연동하여 로깅해 두거나, z_get 폴링 쿼리로 직접 DB를 조회하도록 비즈니스 아키텍처 자체를 변경해야 브릿지의 유실 오류를 근본적으로 회피할 수 있다.

3. tf 데이터 지연 및 로봇 제어 명령 타임아웃 해결

모든 데이터가 ROS2 브릿지를 무난히 넘어갈 수 있어도, 그중에 치명적으로 무겁거나 지연에 민감한 특수 데이터들이 있다. 로봇 팔의 좌표계 변환을 담당하는 /tf 토픽이나 이동 명령인 /cmd_vel 토픽이 대표적이다.

3.0.1 첫째, 무분별한 /tf 토픽 브릿징으로 인한 대역폭 초과 마비

DDS 로컬망을 가득 채우는 /tf는 초당 수백 회, 수백 개의 로봇 조인트 좌표 프레임 변환 매트릭스를 브로드캐스팅한다. 아무리 훌륭한 라우터라도 이 토픽 필터링을 누락하여 글로벌 망(Zenoh Cloud)에 그대로 통과하게 놔두면, 외부 클라우드로 향하는 업링크(Uplink) 대역폭이 100% 포화되며 브릿지 I/O 스레드가 먹통이 된다.

해결책은 확실하다:
원격 관제 측 시각화에서 필요로 하는 최상단 좌표계(예: map -> base_link) 변환 정보만 로컬(로봇 내부)에서 연산하여 /odom 등으로 경량화해 넘겨라. 브릿지의 deny 리스트 정규식 블록에 반드시 ^/tf$^/tf_static$을 최우선으로 선언하여, 헤비급 데이터가 로컬망 밖으로 튀어나가지 못하도록 철창 안에 가두어 버려라.

3.0.2 둘째, 제어 명령(/cmd_vel) 지터 타임아웃 방지

클라우드 조이스틱으로 모터를 조작할 때, 지터를 억제하기 위해 통신 TCP/QUIC 내에서 딜레이 큐가 모이면 이를 백압(Backpressure)으로 버퍼링한다.
하지만 자율주행 정지 제어 명령은 1초 전의 낡은 정지 명령 데이터가 100개 쌓여 전송되는 것보다, 앞선 99개가 다 날아가더라도 지금 당장 누른 가장 최신 1개의 데이터(Latest)가 즉각 도달하는 것이 생명 및 안전에 직결된다.

이 경우 브릿지와 Zenoh 네트워크 라우팅 설정에서 해당 토픽의 데이터 혼잡 제어(Congestion Control) 속성을 지연 대기 모드(Block)가 아닌 Drop 모드로 강제 설정하라. 큐잉 지연을 배제하고 무조건 최신 상태(Latest) 유지 방침을 관철해야 생명을 구할 수 있다.