AEGLOSLAB ROS 2 Jazzy 대전

제3부 미들웨어: DDS·RTPS·RMW

3.5 RMW 계층 — API와 구현 교체

3.5.1 이 장에서 배우는 것 / 선수 지식

이 책은 계속 "벤더마다 다르다"고 말해 왔다. 이 장은 그 경계선을 정확히 긋는다. rmw는 ROS 2가 미들웨어에게 요구하는 계약이며, 그 계약 안이 이식 가능한 영역, 밖이 벤더 영역이다.

실무적 질문 셋에 답한다. ① RMW_IMPLEMENTATION을 바꾸면 정확히 무엇이 달라지는가. ② 서로 다른 RMW를 쓰는 노드끼리 통신되는가. ③ 벤더를 바꿀 때 무엇을 다시 검증해야 하는가.

배우는 것:

  • rmw API의 표면 — 어떤 함수들이 계약을 이루는가.
  • 런타임 로딩 메커니즘 — 재빌드 없이 교체되는 원리.
  • RMW 혼용 — 되는가, 안 되는가, 왜 그런가.
  • 기능 협상 — 벤더가 지원하지 않는 기능은 어떻게 처리되는가.
  • 교체 시 재검증 목록 — 무엇이 실제로 달라지는가.
  • 커스텀 rmw — 만든다면 무엇을 구현해야 하는가(개관).

선수 지식: 2.1(계층), 3.1(DDS), 1.5(QoS), 1.3(typesupport).

정직: rmw API 함수 목록·시그니처는 ros2/rmw 저장소 헤더가 정본이며 배포판마다 추가·변경된다. 아래는 개념적 분류이며 전수가 아니다(확인 필요). RMW 혼용 가능 여부는 특히 조건이 많아 해당 절에서 자세히 다룬다. 이 환경에서 검증하지 않았다(목차 3). 141차(120번째 회차)가 ros:jazzy-ros-base·ros:humble-ros-base에서 이 장의 핵심 주장을 실행 확인했다 — 기본 RMW·환경변수 교체·미설치 시 실패 메시지·RMW 혼용(토픽은 되고 서비스는 실제로 멈춤)까지. 아래 각 절의 정정·추가 블록을 보라.

3.5.2 rmw는 무엇을 약속하는가

rmw는 C 헤더의 모음이다. 구현은 없고, 함수 시그니처와 자료구조만 있다.

런타임 바인딩 rcl (rmw 함수를 호출) rmw 헤더 (선언만) rmw_ fastrtps_ cpp rmw_ cyclonedds_ cpp rmw_ connextdds rmw_ zenoh_cpp

API 표면의 분류:

분류대표 함수역할
초기화rmw_init·rmw_shutdown·rmw_context_fini컨텍스트 수명
노드rmw_create_node·rmw_destroy_node참가자 생성
발행rmw_create_publisher·rmw_publish·rmw_publish_serialized_message데이터 송신
구독rmw_create_subscription·rmw_take·rmw_take_with_info데이터 수신
서비스rmw_create_service·rmw_send_request·rmw_take_response요청·응답(1.7.2)
대기rmw_create_wait_set·rmw_waitexecutor의 기반(2.2.4)
그래프rmw_get_node_names·rmw_get_topic_names_and_types그래프 질의(1.1.8)
GID·비교rmw_get_gid_for_publisher·rmw_compare_gids_equal엔티티 식별(3.2.3)
QoSrmw_qos_profile_check_compatible호환성 판정(1.5.4)
이벤트rmw_take_eventQoS 이벤트(1.4.6)
loanedrmw_borrow_loaned_message·rmw_publish_loaned_message무복사(2.6.5)
보안enclave·security optionsSROS2(13.2)
기능 질의rmw_feature_supported 등지원 여부 확인(확인 필요)

이 목록이 곧 "ROS 2가 미들웨어에게 요구하는 전부"다. 여기 없는 것은 ROS 2 표준 API로 접근할 수 없다 — 3.1.7에서 본 DDS QoS 대부분이 여기 없는 이유다.

반대 방향도 성립한다. rmw에 있는 개념(노드·서비스·액션)은 DDS에 없으므로, rmw_*_cpp가 번역한다. 서비스를 토픽 두 개로 만드는 것(1.7.2), 토픽 이름에 rt/ 접두어를 붙이는 것(2.1.4)이 그 번역이다.

3.5.3 런타임 로딩 — 재빌드가 필요 없는 이유

미설정 설정됨 rcl이 rmw 함수 호출 rmw_implementation (얇은 중개 라이브러리) RMW_ IMPLEMENTATION 환경변수 기본 구현 (빌드 시 결정) dlopen(librmw_XXX.so) 심볼 바인딩 실제 벤더 함수 실행

핵심은 rmw_implementation 패키지다. 이것이 rmw 함수를 구현하되, 내용은 실제 구현 라이브러리로 위임한다.

# 사용 가능한 구현 확인
ros2 doctor --report | grep -i rmw
ls /opt/ros/jazzy/lib/librmw_*.so

# 교체
RMW_IMPLEMENTATION=rmw_cyclonedds_cpp ros2 run demo_nodes_cpp talker

# 현재 무엇을 쓰는지
ros2 doctor --report | grep -i middleware

typesupport도 함께 선택된다(1.3.3). 메시지 타입마다 벤더별 typesupport 라이브러리가 있고, 선택된 RMW의 identifier와 일치하는 것이 런타임에 로드된다. 그래서 RMW를 바꿔도 메시지 패키지를 재빌드할 필요가 없다 — 필요한 typesupport가 이미 설치되어 있다면.

함정: 모든 RMW용 typesupport가 항상 설치되어 있는 것은 아니다. ros-jazzy-rmw-cyclonedds-cpp를 설치하지 않고 RMW_IMPLEMENTATION=rmw_cyclonedds_cpp를 지정하면 로딩에 실패한다(실행 확인, 141차 — Humble·Jazzy 양쪽에서 재현: Error getting RMW implementation identifier / RMW implementation not installed ... failed to load shared library 'librmw_cyclonedds_cpp.so' due to dlopen error: ... No such file or directory. 조용한 폴백이 아니라 즉시·명확한 오류로 실패하고 프로세스가 종료된다 — ros2 topic list 같은 CLI 명령도 예외 없이 이 경로를 그대로 거친다). 커스텀 메시지 패키지도 마찬가지로, 빌드 시점에 설치되어 있던 RMW들의 typesupport만 생성된다(확인 필요: 생성 조건).

실행 확인(141차, Humble·Jazzy 양쪽): ros-jazzy-rmw-cyclonedds-cpp(Humble은 ros-humble-)를 설치한 뒤 RMW_IMPLEMENTATION=rmw_cyclonedds_cpp로 talker/listener를 실행하면 정상적으로 통신한다 — 재빌드 없이 교체된다는 이 절의 핵심 주장이 확인됐다. 다만 Cyclone DDS 쪽은 매 실행마다 [WARN] [rmw_cyclonedds_cpp]: Failed to parse type hash for topic '...' from USER_DATA '(null)' 경고를 그래프의 모든 토픽·서비스에 대해 반복 출력했다(기능에는 영향 없음, 3.5.4의 혼용 실험에서도 동일하게 관찰됨 — Fast DDS 참가자가 광고하는 타입 해시 형식을 Cyclone DDS가 못 읽는 것으로 보인다).

sudo apt install ros-jazzy-rmw-cyclonedds-cpp    # 필요한 구현 설치

함정: 환경변수는 프로세스 단위다. 노드마다 다른 RMW로 실행하는 것이 기술적으로 가능하며, 그것이 다음 절의 문제로 이어진다.

3.5.4 RMW 혼용 — 되는가?

짧은 답: 공식적으로 지원되지 않으며, 기대하지 말아야 한다.

긴 답은 흥미롭다. RTPS는 상호운용 표준이므로(3.2.2) 서로 다른 DDS 벤더가 원칙적으로는 통신할 수 있다. 실제로 조건이 맞으면 되는 경우도 있다. 그런데 조건이 많다.

예 예 아니오 아니오 서로 다른 RMW 노드 둘 다 DDS 기반? ❌ 불가 (예: zenoh ↔ DDS) RTPS 버전 · 인코딩 호환? ❌ 또는 데이터 손상 타입 표현 일치? XCDR1/2 · type hash
예 아니오 아니오 예 타입 표현 일치? XCDR1/2 · type hash ❌ 매칭 실패 ROS 규약 일치? 이름 접두어 · 서비스 구현 ⚠️ 토픽은 되고 서비스는 안 됨 등 ⚠️ 동작할 수 있음 단 비공식
(계속 — 타입 표현 일치 이후)
통신 종류혼용 시
토픽조건이 맞으면 동작할 수 있다(확인 필요)
서비스위험 — ROS 2 서비스는 벤더별 구현 세부(상관 ID 처리)에 의존해 왔다
액션서비스에 의존하므로 동일
그래프 질의참가자 정보 해석이 달라 불완전할 수 있다
loaned·공유메모리벤더 전용이므로 불가
보안설정·플러그인이 달라 사실상 불가

견해: 혼용을 "되는지 시험해 보고 되면 쓰는" 것은 잘못된 결정이다. 개발 중에는 동작하다가 패치 릴리스에서 깨질 수 있고, 그 실패는 조용하다(연결이 안 되거나 데이터가 이상함). 시스템 전체를 하나의 RMW로 통일하라. 서로 다른 미들웨어를 이어야 한다면 명시적 브리지 프로세스를 두는 것이 옳다 — 양쪽에 각각 참여하는 두 프로세스가 데이터를 옮기는 구조(1.1.7의 도메인 브리지와 같은 발상).

혼용을 진단하는 법:

# 각 노드가 무엇을 쓰는지 확인하기 어렵다 — 환경을 확인하는 것이 실질적
cat /proc/$(pgrep -f my_node)/environ | tr '\0' '\n' | grep RMW

# 패킷 캡처에서 벤더 ID 확인 (3.2.3)
# Wireshark: rtps.vendorId

벤더 ID로 확인하는 것이 확실하다. 캡처에서 서로 다른 벤더 ID가 보이면 혼용 중이다.

실행 확인(141차, Humble·Jazzy 양쪽) — 위 표의 "토픽"·"서비스" 행을 직접 재현했다. 같은 컨테이너(같은 네트워크 네임스페이스, 기본 도메인 ID)에서 RMW_IMPLEMENTATION=rmw_fastrtps_cpp(기본)로 demo_nodes_cpp talker를 실행하고, RMW_IMPLEMENTATION=rmw_cyclonedds_cpp로 구독·서비스 클라이언트를 실행했다.

  • 토픽: 실제로 통신됐다. ros2 topic echo /chatter --once(Cyclone 쪽)가 Fast DDS 쪽 talker가 보낸 data: 'Hello World: N'을 정확히 받았다(Humble·Jazzy 둘 다). RTPS가 상호운용 표준이라는 이 절의 설명대로다. 다만 Cyclone DDS 쪽에서 그래프의 모든 엔티티(우리 토픽뿐 아니라 ros_discovery_info·rosout·서비스 요청/응답 토픽까지)에 대해 "Failed to parse type hash ... USER_DATA '(null)'" 경고가 반복됐다 — 통신 자체는 되지만 조용하지 않다.
  • 서비스: 표의 "위험" 경고가 정확히 재현됐다. Fast DDS로 실행한 gap140_trigger 서비스 서버에 대해, Cyclone DDS 클라이언트의 ros2 service list는 그 서비스를 정상적으로 발견했다 — 하지만 실제 ros2 service call /gap140_trigger std_srvs/srv/Trigger {}은 requester: making request: ...를 출력한 뒤 응답을 받지 못하고 타임아웃까지 멈췄다(Humble·Jazzy 둘 다, 8초 대기까지 무응답). 디스커버리(그래프 질의)와 실제 요청·응답 상관관계 처리가 서로 다른 신뢰도로 동작한다는 뜻이다 — 서비스가 "보이는데" 실제로는 안 되는, 표가 경고하는 바로 그 조용한 실패 양상이다.

결론: 이 절의 flowchart가 "⚠️ 동작할 수 있음, 단 비공식"이라 표시한 최종 상태가 토픽에서는 실제로 관찰됐고, 서비스의 "위험" 경고도 실측으로 확인됐다. "짧은 답: 공식적으로 지원되지 않으며 기대하지 말아야 한다"는 여전히 옳다 — 토픽이 된 것은 이번 버전 조합·로컬 네트워크 조건에서일 뿐, 패치 릴리스 하나로 깨질 수 있다는 이 절의 경고 그대로다. 목차 3, tools/gap_harness/gap141.

3.5.5 기능 차이 — 지원하지 않으면 어떻게 되는가

rmw API의 모든 기능을 모든 벤더가 구현하는 것은 아니다.

기능미지원 시 동작
loaned messagecan_loan_messages() false, 일반 경로로 fallback(2.6.5)
공유메모리설정이 무시되고 UDP로
QoS 이벤트 일부콜백이 호출되지 않음(1.4.6)
특정 QoS 정책요청과 다르게 적용되거나 오류
보안활성화 실패
그래프 정보 일부빈 결과 또는 부분 정보

공통 패턴: 조용한 fallback 또는 조용한 무시. 이것이 이 책이 반복해서 "설정했다고 믿지 말고 측정하라"고 말하는 이유다(2.6.7·2.7.8).

방어적 확인 습관:

void log_middleware_capabilities(rclcpp::Node * node,
                                 rclcpp::PublisherBase * pub) {
  RCLCPP_INFO(node->get_logger(), "RMW: %s", rmw_get_implementation_identifier());
  RCLCPP_INFO(node->get_logger(), "loaned message: %s",
              pub->can_loan_messages() ? "지원" : "미지원");
  // 실제 적용된 QoS 확인
  const auto & q = pub->get_actual_qos();
  RCLCPP_INFO(node->get_logger(), "실제 QoS depth=%zu reliability=%d",
              q.depth(), static_cast<int>(q.reliability()));
}

견해: get_actual_qos()를 초기화 로그에 남기는 것을 강하게 권한다. 요청한 QoS와 실제 적용된 QoS가 다를 수 있고(벤더 XML이 우선하는 경우 — 3.7), 그 사실을 나중에 알면 이미 시간을 낭비한 뒤다. RMW·QoS·loan 지원 여부를 시작 로그 한 줄로 남기면 현장 진단이 크게 쉬워진다(1.11.8).

3.5.6 교체 시 재검증 목록

2.1.9에서 "나중에 바꾸면 된다는 위험한 계획"이라고 했다. 구체적으로 무엇을 다시 봐야 하는가:

항목왜 달라지나확인 방법
지연·처리량구현이 다르다종단 지연 측정(2.7.8)
디스커버리 시간·비용참가자 매핑·SPDP 구현 차이캡처(3.3.6)
메모리 사용량캐시·버퍼 정책RSS 추이(2.6.7)
QoS 실제 적용매핑이 다를 수 있다topic info -v·get_actual_qos()
대용량 메시지조각화·흐름 제어 기본값(3.2.7)실제 전송 시험
무선·유실 환경재전송 정책부하·유실 환경 시험
튜닝 설정XML 전체가 무효재작성 필요(3.7→3.8)
loaned·공유메모리지원 여부·조건can_loan_messages()
보안 설정플러그인·설정 형식SROS2 재검증(13.3)
Discovery ServerFast DDS 전용대안 필요(3.4)
대규모 안정성스케일 거동이 다르다장시간·대규모 시험
알려진 이슈벤더별로 다름이슈 트래커 확인

"XML 전체가 무효"가 가장 큰 비용이다. Fast DDS 프로파일과 Cyclone 설정은 형식도 개념 구성도 다르므로 번역이 아니라 재작성이다(3.7·3.8).

견해: RMW 교체는 "환경변수 하나"가 아니라 "성능·안정성 재검증 프로젝트"로 계획해야 한다. 교체 자체는 5초지만 신뢰를 회복하는 데는 며칠이 든다. 그래서 벤더 선택은 초기에, 근거를 갖고 하는 편이 낫다(3.6).

교체를 쉽게 만드는 설계 습관:

  1. 벤더 XML에 의존하는 설정을 최소화하고, 가능하면 ROS 2 표준 QoS로 표현한다.
  2. 벤더 전용 기능(Discovery Server·공유메모리)을 쓴다면 그 사실을 문서화하고 대안을 적어 둔다.
  3. 시작 로그에 RMW·실제 QoS를 남긴다(3.5.5).
  4. 성능 기준선(baseline)을 측정해 둔다 — 교체 후 비교 대상이 있어야 한다.

3.5.7 커스텀 rmw — 개관

새 미들웨어 백엔드를 만드는 것은 가능하며, 실제로 여러 구현이 존재한다(zenoh가 최근 예 — 3.10).

필요한 것:

항목내용
rmw 함수 전부 구현미지원은 적절한 오류 반환
typesupport 제공자체 직렬화 또는 기존 재사용(1.3.3)
identifier 등록rmw_get_implementation_identifier()
패키지 구성rmw_implementation이 찾을 수 있게
ROS 규약 구현이름 접두어·서비스 매핑·그래프 질의

현실적 난이도: 매우 높다. 함수 개수도 많지만, ROS 2가 기대하는 의미론(QoS 호환 규칙·그래프 일관성·서비스 상관)을 정확히 재현하는 것이 어렵다.

만들 만한 경우:

  • 사내 전용 미들웨어가 이미 있고 ROS 2를 도입해야 할 때.
  • 특수 전송(공유 버스·백플레인·위성 링크)이 필요할 때.
  • 연구 목적.

대안을 먼저 검토하라: 대부분의 경우 기존 RMW + 브리지 노드가 훨씬 비용이 낮다. "우리 프로토콜을 쓰는 노드를 하나 만들고 ROS 토픽으로 변환"하는 구조는 며칠이면 되지만, rmw 구현은 수개월이다.

상세는 15.4에서 다룬다.

3.5.8 진단 — 무엇을 쓰고 있고 잘 동작하는가

# 1. 현재 구현
ros2 doctor --report | grep -iA2 middleware
echo $RMW_IMPLEMENTATION

# 2. 설치된 구현
ls /opt/ros/jazzy/lib/ | grep librmw_

# 3. 프로세스별 확인 (혼용 탐지)
for p in $(pgrep -f ros2); do
  echo -n "$p: "; tr '\0' '\n' < /proc/$p/environ | grep RMW_IMPLEMENTATION || echo "(기본)"
done

# 4. 실제 QoS 적용 확인
ros2 topic info -v /scan

# 5. 벤더 ID (패킷 수준)
sudo tcpdump -i any -w v.pcap 'udp portrange 7400-7700'
# Wireshark 필터: rtps.vendorId

실행 확인(141차). 1·2·3번은 Humble·Jazzy ros-base 이미지에서 그대로 동작했다 — ros2 doctor --report는 middleware name : rmw_fastrtps_cpp를 정확히 보고했고(RMW_IMPLEMENTATION 미설정 상태의 기본값), ls .../lib/ | grep librmw_는 설치된 구현만 나열했다(기본 이미지엔 rmw_fastrtps_cpp 계열만 설치되어 있고 rmw_cyclonedds_cpp는 apt install 후에야 나타났다). 4·5번(get_actual_qos()·tcpdump 벤더 ID)은 이번 회차에 재현하지 않았다(확인 필요).

3번이 특히 유용하다. 여러 터미널·launch·컨테이너에서 노드를 실행하다 보면 환경이 일관되지 않은 경우가 생기고, 그것이 "일부 노드만 안 보인다"의 원인일 수 있다.

3.5.9 함정 카탈로그

#함정증상대처
1구현 패키지 미설치RMW 로딩 실패apt install ros-jazzy-rmw-*
2RMW 혼용조용한 통신 실패·서비스 불가전체 통일(3.5.4)
3환경변수 일부 노드만 적용그래프 분열프로세스별 확인
4미지원 기능을 지원으로 가정조용한 fallback능력 확인·로그
5요청 QoS = 실제 QoS로 가정XML이 우선할 수 있음get_actual_qos()
6교체를 환경변수 문제로 취급성능·안정성 회귀재검증 프로젝트로
7XML 튜닝 후 벤더 교체설정 전부 무효재작성 예산
8Discovery Server 후 벤더 교체디스커버리 구조 붕괴대안 준비(3.4)
9커스텀 rmw를 성급히 착수수개월 소모브리지 노드 먼저
10기준선 없이 교체개선·악화 판단 불가사전 측정

3.5.10 체크포인트

  • rmw API의 분류 열 가지 이상을 들 수 있는가. "여기 없는 것은 ROS 2 표준으로 접근 불가"라는 명제의 예를 댈 수 있는가.
  • rmw에는 있지만 DDS에는 없는 개념 세 가지와, rmw_*_cpp가 그것을 어떻게 번역하는지 말할 수 있는가.
  • RMW_IMPLEMENTATION 교체가 재빌드 없이 되는 원리를 typesupport와 함께 설명할 수 있는가. 실패하는 경우는?
  • RMW 혼용이 공식 지원되지 않는데도 "가끔 되는" 이유는 무엇인가. 토픽·서비스·보안 각각의 위험도는?
  • 혼용을 시험해 보고 쓰는 것이 왜 잘못된 결정인가. 올바른 대안은?
  • 벤더가 기능을 지원하지 않을 때의 공통 패턴은 무엇인가. 그것이 이 책의 어떤 반복된 조언과 연결되는가.
  • 시작 로그에 남기면 좋은 미들웨어 정보 세 가지는?
  • RMW 교체 시 재검증해야 할 항목 열두 가지 중, 비용이 가장 큰 것은 무엇이며 왜인가.
  • 교체를 쉽게 만드는 설계 습관 네 가지를 말할 수 있는가.
  • 커스텀 rmw 대신 브리지 노드를 먼저 검토해야 하는 이유는?

다음 장 3.6은 이 계약 위에 선 실제 벤더들을 비교한다 — Fast DDS·Cyclone DDS·Connext·GurumDDS·zenoh를 어떤 축으로 평가하고, 어떻게 자신의 시스템에서 판단할 것인가.