AEGLOSLAB ROS 2 Jazzy 대전

제1부 계산 그래프의 코어

1.4 토픽 — 발행과 구독의 전 경로

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

토픽은 ROS 2에서 가장 많이 쓰이는 통신 수단이고, 가장 많이 오해되는 수단이기도 하다. "발행했는데 안 온다", "가끔 몇 개가 빠진다", "구독 콜백이 지연된다"는 문제의 대부분은 토픽을 함수 호출처럼 상상하는 데서 온다. 토픽은 함수 호출이 아니다. 그 사이에는 큐가 여럿 있고, 스레드 경계가 여럿 있고, 버려도 되는 지점이 명시적으로 설계되어 있다.

이 장은 publish() 한 줄이 구독 콜백에 도달하기까지의 전 경로를 계층별로 따라간다. QoS는 이 경로 곳곳에서 동작을 바꾸는데, 정식 설명은 1.5에 두고 여기서는 "경로의 어느 지점에서 작용하는가"만 표시한다.

배우는 것:

  • 발행 경로 전체 — rclcpp 객체에서 DDS DataWriter, 그리고 네트워크까지.
  • 구독 경로 전체 — DDS 수신에서 wait set, executor, 콜백까지. 콜백이 왜 즉시 불리지 않는가.
  • 큐가 몇 개이고 어디서 버려지는가 — 유실의 정확한 발생 지점 목록.
  • 콜백 시그니처의 종류와 그 선택이 복사·소유권에 미치는 영향.
  • QoS 이벤트와 그 밖의 이벤트 — 통신이 조용히 실패할 때 이를 알아채는 공식 통로 전부(matched·incompatible_type 포함).
  • 발행 직후 유실 문제의 원인과 올바른 해법.
  • 토픽을 쓰면 안 되는 경우 — 토픽·서비스·액션의 경계.

선수 지식: 1.1(이름), 1.2·1.3(메시지·직렬화). 실행 모델(executor)은 2.2~2.4에서 본격적으로 다루지만, 이 장에서 그 필요성을 먼저 느끼게 된다.

정직: 아래 계층 서술은 rclcpp·rcl·rmw 소스 구조와 DDS 명세에 근거한 개념적 경로다. 함수 이름·호출 순서는 버전에 따라 달라질 수 있으며 이 환경에서 실행 추적으로 검증하지 않았다(목차 3). 실제 경로를 확인하는 정본 수단은 ros2_tracing(11.6)이다.

검증 상태(추가): 이 장의 서술 가운데 3곳은 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).

1.4.2 발행 경로 — publish()부터 와이어까지

예 아니오 pub->publish(msg) intra-process 활성 + 내부 구독자? 프로세스 내 큐에 포인터 전달 rcl_publish rmw_publish typesupport 직렬화 (CDR) DDS DataWriter history 큐에 적재 RTPS 송신 UDP·공유메모리

각 단계에서 실제로 일어나는 일과 주의할 점:

단계하는 일함정
publish()intra-process 분기 판단, 소유권 처리const& 발행은 복사 발생(1.4.5)
rcl_publish핸들 검증, 로깅 훅—
rmw_publish벤더 API 호출벤더별 동작 차이 시작
직렬화CDR 인코딩(1.3)큰 메시지에서 CPU 지배적
DataWriter 큐history depth만큼 보관여기가 첫 유실 지점
RTPS 송신조각화·전송MTU 초과 시 조각화(3.2)

핵심 사실 셋:

  1. publish()는 대개 비동기다. 반환됐다고 전송이 끝난 것이 아니다. 데이터는 DataWriter의 히스토리 큐에 들어갔을 뿐이고, 실제 전송·재전송은 DDS 내부 스레드가 처리한다. 그래서 publish() 직후 메시지 객체를 수정·소멸시켜도 되며(복사 또는 소유권 이전이 끝난 상태), 반대로 "보냈으니 도착했다"는 추론은 성립하지 않는다.
  2. publish()가 블로킹될 수 있다. RELIABLE + KEEP_ALL에서 히스토리가 가득 차면 공간이 날 때까지 기다린다. 실시간 루프에서 이것이 발생하면 주기를 놓친다(12.1). 이 조합은 실시간 경로에 두면 안 되는 대표적 설정이다.
  3. 발행 자체는 구독자 유무를 신경 쓰지 않는다. 구독자가 0이어도 직렬화는 일어난다(intra-process 경로가 아닌 한). 그래서 아무도 안 듣는 대용량 토픽이 CPU를 계속 점유하는 일이 생긴다. 이를 피하려는 것이 lazy publisher 패턴이다.
// lazy publisher — 구독자가 있을 때만 일한다
if (pub_->get_subscription_count() > 0) {
  auto msg = build_expensive_message();   // 비용이 큰 준비 작업 자체를 건너뜀
  pub_->publish(std::move(msg));
}

image_transport가 이 패턴을 내장한다(7.2). 부작용은 0.7.4에서 본 것 — ros2 topic echo를 거는 순간 노드의 동작이 실제로 바뀐다.

1.4.3 구독 경로 — 왜 콜백이 즉시 불리지 않는가

여기가 ROS 2 초심자가 가장 자주 오해하는 지점이다. 데이터가 도착해도 콜백은 자동으로 실행되지 않는다.

RTPS 수신 (DDS 내부 스레드) 역직렬화 DataReader history 큐 적재 조건변수 신호 wait set 깨어남 executor가 준비된 엔티티 수집 콜백 그룹 정책 확인 콜백 실행 (executor 스레드)

결정적 사실: 콜백은 DDS 스레드가 아니라 executor 스레드에서 실행된다. 즉 spin()을 실행하는 그 스레드다. spin()을 호출하지 않으면 데이터는 도착하지만 콜백은 영원히 안 불린다 — 큐에 쌓이다 오래된 것부터 버려진다.

auto node = std::make_shared<MyNode>();
// rclcpp::spin(node);   ← 이 줄을 빼면 콜백이 절대 실행되지 않는다
rclcpp::shutdown();

이 사실에서 파생되는 실무 문제들:

증상원인
콜백이 전혀 안 불림spin() 누락, 또는 다른 곳에서 블로킹
콜백이 늦게 불림다른 콜백이 executor를 점유(2.3)
고빈도 토픽 일부가 유실콜백 처리 시간 > 발행 주기 → 큐 넘침
콜백 안에서 서비스 호출 시 멈춤단일 스레드 executor의 자기 교착(1.7)
여러 토픽 중 하나가 기아 상태executor의 라운드로빈·우선순위 특성(2.2)

"콜백 처리 시간 > 발행 주기"는 유실의 가장 흔한 원인이다. 30 Hz 이미지 콜백에서 40 ms를 쓰면 큐가 자라고, depth를 넘으면 버려진다. QoS depth를 키우는 것은 해결이 아니라 지연을 늘리는 것이다 — 처리를 빠르게 하거나, 콜백 그룹으로 병렬화하거나(2.4), 애초에 받는 양을 줄여야 한다.

1.4.4 큐는 몇 개이고 어디서 버려지는가

"메시지가 사라졌다"를 진단하려면 버려질 수 있는 모든 지점을 알아야 한다.

publish() ① DataWriter 히스토리 depth 초과 시 폐기 ② 네트워크 UDP 유실·버퍼 오버플로 ③ DataReader 히스토리 depth 초과 시 폐기 ④ executor 대기 처리 지연 → ③ 유발 콜백
#지점언제 버려지나관측 방법대처
①DataWriter 히스토리KEEP_LAST depth 초과, 아직 전송·확인 안 된 샘플QoS 이벤트(1.4.6), 벤더 통계depth 조정, 발행률 감소
②네트워크·OS 버퍼UDP 유실, 소켓 버퍼 초과, 무선 간섭ss -u -m, netstat -su, 인터페이스 통계소켓 버퍼 확대(3.9), RELIABLE
③DataReader 히스토리depth 초과message_lost 이벤트(1.4.6) — DDS의 sample_lost·sample_rejected는 rmw가 노출하지 않는다(실행 확인)depth 조정, 처리 가속
④executor 지연콜백이 느려 ③을 유발트레이싱(11.6)병렬화·경량화

RELIABLE도 모든 경우를 해결하지는 못한다. RELIABLE은 ②(네트워크 유실)를 재전송으로 복구하지만 ①·③(큐 넘침)은 막지 못한다. KEEP_LAST인 이상 오래된 것은 버려진다. "신뢰성으로 설정했는데 왜 빠지나"의 답이 대개 이것이다.

소켓 버퍼는 자주 놓치는 지점이다. 대용량 토픽(포인트클라우드·이미지)에서 커널 UDP 수신 버퍼가 작으면 애플리케이션이 읽기 전에 커널이 버린다.

sysctl net.core.rmem_max net.core.rmem_default
netstat -su | grep -i "receive errors\|buffer errors"

이 값을 키우는 것은 DDS 설정 이전에 해야 할 OS 튜닝이며, 벤더 문서들도 이를 먼저 권한다(3.7·3.8·3.9).

1.4.5 콜백 시그니처와 소유권 — 복사를 어디서 없애나

발행 측:

// ① const& — 라이브러리가 복사한다. 안전하지만 복사 1회.
auto msg = sensor_msgs::msg::Imu();
pub_->publish(msg);

// ② unique_ptr — 소유권 이전. intra-process에서 0-copy 가능.
auto msg = std::make_unique<sensor_msgs::msg::Imu>();
pub_->publish(std::move(msg));

// ③ loaned message — 미들웨어가 준 버퍼에 직접 씀 (2.6)
auto loaned = pub_->borrow_loaned_message();
loaned.get().linear_acceleration.x = 1.0;
pub_->publish(std::move(loaned));

구독 측:

// ① const shared_ptr& — 가장 흔함. 여러 구독자와 공유.
[](const sensor_msgs::msg::Imu::ConstSharedPtr msg) { … }

// ② unique_ptr — 이 콜백만 받을 때 소유권을 넘겨받음(수정 가능)
[](sensor_msgs::msg::Imu::UniquePtr msg) { … }

// ③ 값·const& — 복사가 일어난다
[](const sensor_msgs::msg::Imu & msg) { … }
조합intra-process 복사 횟수비고
unique_ptr 발행 + unique_ptr 구독 1개0최적 경로
unique_ptr 발행 + shared_ptr 구독 N개0(공유)구독자들이 같은 객체를 본다 — 수정 금지
unique_ptr 발행 + unique_ptr 구독 N개(N>1)N-1각자 소유해야 하므로 복사
const& 발행최소 1라이브러리가 내부 복사

함정: shared_ptr로 받은 메시지를 수정하지 말 것. intra-process에서는 다른 구독자와 같은 객체를 보고 있을 수 있다. 수정이 필요하면 unique_ptr 콜백을 쓰거나 복사한다. 이 버그는 프로세스 배치를 바꾸는 순간 나타나거나 사라져서 재현이 극도로 어렵다.

함정: 네트워크 경로에서는 위 최적화가 대부분 무의미하다. 어차피 직렬화·복사가 일어난다. unique_ptr 발행의 이득은 주로 intra-process·loaned 경로에서 나온다(2.7).

1.4.6 QoS 이벤트 — 조용한 실패를 알아채는 법

토픽 통신이 실패할 때 예외도, 반환값도, 로그도 없다. 발행자는 계속 발행하고 구독자는 계속 기다린다. 이 침묵을 깨는 공식 통로가 QoS 이벤트 콜백이다.

rclcpp::SubscriptionOptions options;

options.event_callbacks.incompatible_qos_callback =
  [this](rclcpp::QOSRequestedIncompatibleQoSInfo & info) {
    RCLCPP_ERROR(get_logger(),
      "QoS 불일치! 문제 정책 id=%d, 누적 %d회",
      info.last_policy_kind, info.total_count);
  };

options.event_callbacks.message_lost_callback =
  [this](rclcpp::QOSMessageLostInfo & info) {
    RCLCPP_WARN(get_logger(), "메시지 유실 %zu개", info.total_count_change);
  };

options.event_callbacks.deadline_callback = …;   // 데드라인 미충족
options.event_callbacks.liveliness_callback = …; // 상대가 종료됐다고 판단됨

sub_ = create_subscription<sensor_msgs::msg::Imu>(
  "imu", rclcpp::SensorDataQoS(), callback, options);

발행자 쪽에도 대응 이벤트가 있다(offered_incompatible_qos, offered_deadline_missed, liveliness_lost).

Jazzy의 콜백 집합은 여기서 끝나지 않는다. rclcpp/event_handler.hpp가 정의하는 구조체 두 개가 전부다 — 원문 확인.

구조체콜백
PublisherEventCallbacksdeadline_callback · liveliness_callback · incompatible_qos_callback · incompatible_type_callback · matched_callback
SubscriptionEventCallbacksdeadline_callback · liveliness_callback · incompatible_qos_callback · message_lost_callback · incompatible_type_callback · matched_callback
이벤트의미실무 활용
incompatible_qosQoS가 안 맞아 연결 자체가 안 됨최우선 — 이것 하나로 "안 온다" 문제의 상당수가 즉시 진단된다
matched상대 엔드포인트가 연결되거나 떨어졌다"아무도 안 듣고 있다"·"발행자가 사라졌다"를 즉시 안다
message_lost미들웨어가 유실을 감지대역·버퍼 문제의 조기 경보
deadline_missed약속한 주기를 못 지킴센서 정지·부하 감지(1.6)
liveliness_changed상대 노드의 생존 상태 변화페일오버 트리거
incompatible_type같은 토픽 이름에 다른 타입이 등록됨인터페이스 버전 불일치 감지(1.3.5)

matched — 이 장의 논지에 가장 가까운 이벤트. "조용한 실패"의 첫 번째 형태는 QoS 불일치가 아니라 애초에 상대가 없는 것이다. 이 이벤트가 그것을 알려 준다.

options.event_callbacks.matched_callback =
  [this](rclcpp::MatchedInfo & info) {
    RCLCPP_INFO(get_logger(), "matched: 현재 %zu, 변화 %d",
      info.current_count, info.current_count_change);
    if (info.current_count == 0) {
      RCLCPP_WARN(get_logger(), "짝이 하나도 없다 — 이름·네임스페이스·도메인 확인");
    }
  };

rclcpp::MatchedInfo(= rmw_matched_status_t)는 네 값을 준다 — 원문 확인.

필드의미
total_count지금까지 매칭된 누적 수
total_count_change마지막 조회 이후 누적 증가분
current_count현재 연결된 상대 수 — 실무에서 보는 값
current_count_change현재 수의 변화(음수면 떨어진 것)

실행 확인(2026-08-16): Jazzy 컨테이너에서 위 콜백을 컴파일·실행해 구독이 발행자와 매칭되는 순간 matched cur=1 chg=1이 출력되는 것을 확인했다. 같은 실행에서 생성자 안에서 발행한 메시지는 도착하지 않았다 — 매칭 전이었기 때문이다. 이것이 1.4.7에서 다루는 "발행 직후 유실"이며, matched가 바로 그 순간을 관측하는 수단이다.

견해: matched는 lazy 발행(1.4.2)의 표준 구현이다. get_subscription_count()를 주기적으로 폴링하는 대신 이 콜백에서 부하가 큰 파이프라인을 켜고 끄면, 폴링 주기만큼의 지연도 없고 코드도 짧다. 카메라·포인트클라우드처럼 구독자가 없으면 처음부터 만들지 말아야 할 데이터에 특히 값을 한다(7.8.3.4).

incompatible_type — 같은 이름, 다른 타입. type hash(1.3.5)가 감지하는 상황을 이벤트로 받는다. 다만 동작 범위가 좁고 경계가 분명하다.

상황Jazzy에서 관측된 것
같은 프로세스에서 같은 토픽 이름에 다른 타입 구독 생성이벤트가 아니라 예외 — create subscription() called for existing topic name … with incompatible type으로 생성 자체가 실패
다른 프로세스에서 타입 이름이 서로 다름아무 이벤트도 발생하지 않았다 — 애초에 매칭이 성립하지 않으므로 조용하다

실행 확인(2026-08-16): 두 경우 모두 실행해 위 결과를 얻었다. rmw_fastrtps는 이 이벤트를 DDS의 inconsistent_topic 상태에 연결한다(라이브러리 심볼 확인). 따라서 이 이벤트를 "타입이 다르면 알려 주는 안전망"으로 신뢰하면 안 된다 — 이름이 다른 타입은 조용히 서로를 못 볼 뿐이다. (확인 필요): 이름은 같고 정의(type hash)만 다른 경우의 거동은 확인하지 못했다.

함정: "이벤트를 등록했으니 이상은 다 검출된다"가 가장 위험한 오해다. 위 표가 보여 주듯 가장 흔한 실수 — 토픽 이름 오타(1.1.5) — 는 어떤 이벤트도 발생시키지 않는다. 이름이 다르면 단순히 서로 없는 것이다. 이때 유일한 신호가 matched의 current_count == 0이다.

견해: 제품에 나가는 노드라면 최소한 incompatible_qos와 message_lost는 붙이라. 구현 비용은 몇 줄이고, 현장에서 이 두 로그의 유무가 진단 시간을 시간 단위로 가른다. 이것이 없으면 유일한 진단 수단은 ros2 topic info -v를 사람이 직접 보는 것뿐이다(0.7.5).

정직: 이벤트 지원 범위는 RMW 벤더에 따라 다를 수 있다(확인 필요). 특정 이벤트가 호출되지 않는다면 지원 여부를 먼저 확인해야 한다.

1.4.7 발행 직후 유실 — 원인과 올바른 해법

전형적 실수:

// 노드 생성 직후 한 번 발행하고 끝 — 아무도 못 받는다
auto pub = node->create_publisher<std_msgs::msg::String>("startup_info", 10);
pub->publish(msg);   // 구독자가 아직 디스커버리되지 않았다

원인은 디스커버리가 즉시 완료되지 않기 때문이다(3.3). 발행자와 구독자가 서로를 알아보기까지 수백 밀리초~수 초가 걸릴 수 있고, 그 전에 발행한 것은 갈 곳이 없다.

해법 세 가지와 그 성질:

① TRANSIENT_LOCAL 내구성(권장) — 발행자가 마지막 N개를 보관하고, 나중에 참여하는 구독자에게도 전달한다.

auto qos = rclcpp::QoS(1).transient_local().reliable();
auto pub = node->create_publisher<std_msgs::msg::String>("startup_info", qos);
  • 장점: 근본 해결. 구독자가 언제 연결되든 받는다. 지도(/map)·로봇 기술(/robot_description)이 이 방식을 쓴다.
  • 단점: 구독자도 TRANSIENT_LOCAL이어야 한다(1.5의 호환 규칙). 발행자가 메모리를 계속 점유한다. 대용량에는 부적합.

② 반복 발행 — 타이머로 주기적으로 다시 보낸다.

  • 장점: 단순, QoS 제약 없음.
  • 단점: 대역 낭비, "언제까지 반복하나" 문제. 늦게 참여한 구독자가 다음 주기까지 기다린다.

③ 구독자 수 대기 — count_subscribers()가 0이 아닐 때까지 기다렸다 발행.

  • 장점: 직관적.
  • 단점: 경합을 줄일 뿐 없애지 못한다(1.1.8). 구독자 수를 미리 알아야 하고, 나중에 참여하는 구독자는 여전히 못 받는다. 권장하지 않는다.

견해: ①이 정답이고, ②는 임시방편, ③은 안티패턴이다. "한 번만 발행하는 상태 정보"는 그 자체로 TRANSIENT_LOCAL을 요구하는 신호로 읽는 것이 좋다(1.6).

1.4.8 토픽·서비스·액션 — 언제 토픽이 아닌가

토픽은 강력하지만 모든 경우를 해결하지는 못한다. 잘못 고르면 구조가 뒤틀린다.

성질토픽서비스(1.7)액션(1.8)
방향단방향요청·응답요청·피드백·결과
대상 수다대다1:11:1(다중 목표 가능)
응답 보장없음있음(연결되면)있음
소요 시간즉시짧아야 함길어도 됨
취소개념 없음없음있음
진행 상황없음없음피드백
상태 유지없음없음목표별 상태기계

토픽을 쓰면 안 되는 신호:

  • "이 명령이 접수됐는지 확인해야 한다" → 서비스나 액션. 토픽에는 도착 확인이 없다.
  • "여러 클라이언트가 각자 다른 답을 받아야 한다" → 서비스. 토픽은 모두에게 같은 것을 보낸다.
  • "오래 걸리고 중간에 취소할 수 있어야 한다" → 액션.
  • "요청·응답을 토픽 두 개로 흉내 내고 있다" → 상관관계·타임아웃·중복 처리를 직접 만들게 된다. 서비스나 액션을 쓰라.

반대로 토픽이 맞는 신호:

  • 주기적 데이터 스트림(센서·상태·명령).
  • 소비자 수가 유동적이거나 0일 수 있다.
  • 유실이 허용되거나, 최신 값만 의미가 있다.
  • 발행자가 소비자의 존재·처리 결과를 알 필요가 없다.

함정: "명령을 토픽으로"는 흔하지만 위험할 수 있다. /cmd_vel이 대표적이다 — 유실되면 로봇이 이전 명령으로 계속 달린다. 그래서 안전한 시스템은 명령 토픽에 타임아웃(워치독)을 반드시 둔다: 일정 시간 명령이 없으면 정지. 이는 토픽 선택이 틀렸다는 뜻이 아니라, 토픽의 성질(무보장)을 시스템 설계로 보완해야 한다는 뜻이다(14.1·14.10).

1.4.9 진단 절차 — "토픽이 안 온다"의 순서

아니오 예 한쪽만 있음 QoS 다름 양쪽 정상 흐름 안 흐름 데이터가 안 온다 ros2 topic list 토픽이 보이나? 도메인·RMW· 네트워크 0.4 · 3.9 ros2 topic info -v 양쪽 엔드포인트·QoS 상대 노드 미실행 또는 이름 불일치 1.1 QoS 불일치 1.5 ros2 topic hz 실제로 흐르나? 구독 측 문제 spin·executor 2.3 발행 측 문제 로직·lazy· 타이머 1.10

이 순서를 지키면 대부분 3단계 안에 원인이 좁혀진다. 가장 흔한 정답은 F(QoS 불일치)와 H(executor) 이며, 초심자는 대개 I(코드 버그)부터 의심해 시간을 낭비한다. 전체 디버깅 절차는 11.1에서 확장한다.

1.4.10 함정 카탈로그

#함정증상대처
1spin() 없이 구독콜백이 전혀 안 돎executor 실행(2.2)
2콜백이 발행 주기보다 느림조용한 유실병렬화·경량화(2.4)
3depth를 키워 유실 대응유실 대신 지연 증가근본 원인 처리
4노드 생성 직후 1회 발행아무도 못 받음TRANSIENT_LOCAL(1.4.7)
5shared_ptr 콜백에서 메시지 수정다른 구독자 데이터 오염unique_ptr 콜백 또는 복사
6RELIABLE+KEEP_ALL을 실시간 루프에publish() 블로킹 → 주기 이탈KEEP_LAST·BEST_EFFORT(1.6)
7QoS 이벤트 미설정조용한 실패를 못 알아챔이벤트 콜백 등록(1.4.6)
8소켓 버퍼 미조정대용량 토픽에서 커널 유실net.core.rmem_max(3.9)
9요청·응답을 토픽 2개로 구현상관·타임아웃 로직 자작서비스·액션(1.7·1.8)
10명령 토픽에 워치독 없음통신 두절 시 폭주타임아웃 정지(14.10)
11구독자 0인데 대용량 발행CPU 낭비lazy publisher(1.4.2)
12ros2 topic hz만 믿고 판단실제 콜백 지연을 못 봄트레이싱(11.6)
13토픽 이름 오타어떤 이벤트도 안 뜸 — 단순히 서로 없는 것matched의 current_count == 0 감시(1.4.6)
14incompatible_type을 타입 안전망으로 신뢰이름이 다른 타입은 조용히 무시됨인터페이스 버전 고정(1.2.8)

1.4.11 체크포인트

  • publish()가 반환된 시점에 무엇이 보장되고 무엇이 보장되지 않는가를 말할 수 있는가.
  • publish()가 블로킹될 수 있는 QoS 조합은 무엇이며, 왜 실시간 경로에서 위험한가.
  • "콜백은 DDS 스레드가 아니라 executor 스레드에서 실행된다"는 사실에서 파생되는 문제 세 가지를 들 수 있는가.
  • 메시지가 버려질 수 있는 네 지점을 순서대로 말하고, 각각의 관측 수단을 댈 수 있는가.
  • RELIABLE로 설정해도 유실이 남는 이유를 설명할 수 있는가.
  • intra-process에서 복사를 0으로 만드는 발행·구독 조합은 무엇인가. 구독자가 여럿이면 어떻게 되는가.
  • shared_ptr 콜백에서 메시지를 수정하면 안 되는 이유와, 그 버그가 왜 재현하기 어려운가를 설명할 수 있는가.
  • QoS 이벤트 중 최우선으로 붙여야 할 두 가지는 무엇이며 각각 무엇을 알려주는가.
  • "노드 시작 직후 한 번 발행한 값을 아무도 못 받는다"의 올바른 해법과, 구독자 수 폴링이 왜 안티패턴인가를 말할 수 있는가.
  • 토픽 대신 서비스·액션을 써야 한다는 신호 네 가지를 들 수 있는가. /cmd_vel 같은 명령 토픽에 반드시 필요한 보완 장치는 무엇인가.
  • "토픽이 안 온다"를 만났을 때 실행할 명령을 순서대로 셋 이상 말할 수 있는가.

다음 두 장(1.5·1.6)은 이 경로 전체의 동작을 결정하는 QoS를 다룬다. 먼저 정책 전수와 호환성 규칙(1.5), 그 다음 실전 프로파일 설계와 함정(1.6).