제1부 계산 그래프의 코어
1.6 QoS 심층 II — 프로파일 설계와 함정
1.6.1 이 장에서 배우는 것 / 선수 지식
1.5가 "정책이 무엇인가"였다면 이 장은 "내 시스템에는 무엇을 쓸 것인가" 다. QoS 설계는 정답이 하나인 문제가 아니라, 무엇을 잃어도 되는지 정하는 문제다 — 대역·지연·메모리·데이터 중 무엇을 포기할지 결정하고 그것을 정책으로 표현하는 일이다.
배우는 것:
- 데이터 종류별 프로파일 설계 — 센서·명령·상태·지도·진단·기록, 각각의 근거.
- depth를 지연 예산에서 역산하는 법 — "10이면 되겠지"를 대체하는 계산.
- 무선·손실 링크에서의 QoS —
RELIABLE이 왜 역효과를 낼 수 있는가. TRANSIENT_LOCAL의 숨은 비용 — 메모리·늦은 구독자·재시작 시 동작.- 시스템 차원의 QoS 일관성 관리 — 팀 규모에서 QoS가 어긋나지 않게 하는 방법.
- QoS 결정 절차 — 처음 보는 토픽에 프로파일을 정하는 순서.
- 표준 프로파일 런타임 확인(1.6.11) — 표가 맞는지,
/clock이 왜 특별한지. system_default의 보고값과 실제 동작이 어긋난다(1.6.12) — 진단 명령을 믿을 수 없는 유일한 프로파일.
선수 지식: 1.5(정책·호환성 규칙), 1.4(경로와 큐 구조).
정직: 이 장의 수치(depth·주기·타임아웃)는 설계 방법을 보이기 위한 예시이며 보편 권고가 아니다. 실제 값은 하드웨어·네트워크·처리 지연에 따라 달라진다. 성능 수치는 이 환경에서 측정 검증하지 않았다(목차 3). 각 절은 값이 아니라 값을 정하는 논리를 제시한다. 다만 표준 프로파일의 구성값은 Jazzy 런타임에서 확인했다(실행 확인 — 1.6.11).
1.6.2 설계의 출발점 — 네 가지 질문
새 토픽의 QoS를 정할 때 순서대로 답한다.
| 질문 | 답이 '아니오'면 | 답이 '예'면 |
|---|---|---|
| ① 오래된 데이터가 가치 있나 | KEEP_LAST(작게) + 짧은 lifespan | KEEP_LAST(크게) 또는 KEEP_ALL |
| ② 하나라도 놓치면 안 되나 | BEST_EFFORT | RELIABLE |
| ③ 늦은 구독자가 과거를 알아야 하나 | VOLATILE | TRANSIENT_LOCAL |
| ④ 안 오면 알아야 하나 | 기본 | deadline + 이벤트 콜백 |
①과 ②는 독립이다. "중요한 데이터"라고 무조건 RELIABLE이 아니다. 레이저 스캔은 로봇 안전에 결정적이지만, 한 프레임을 재전송받느니 다음 프레임을 받는 것이 낫다 — 그래서 BEST_EFFORT가 맞다. 반대로 "지도 저장 완료 알림"은 크기도 작고 빈도도 낮지만 놓치면 안 되므로 RELIABLE이다.
1.6.3 데이터 종류별 프로파일
1.6.3.1 고빈도 센서 스트림 (LiDAR·카메라·IMU)
auto qos = rclcpp::SensorDataQoS(); // BEST_EFFORT, KEEP_LAST(5), VOLATILE
// 정지 감지를 원하면
qos.deadline(std::chrono::milliseconds(150)); // 10 Hz 센서 기준 여유 1.5배
근거:
BEST_EFFORT— 재전송받은 옛 스캔은 쓸모가 없다. 최신성이 완결성보다 중요하다.- 작은 depth — 큐에 쌓인다는 것은 이미 처리를 못 따라간다는 뜻이다. 깊게 설정하면 오래된 데이터를 처리하느라 더 뒤처진다.
VOLATILE— 과거 스캔은 의미 없다.deadline— 센서 프리즈·USB 끊김을 이벤트로 감지한다(6.9).
함정: 드라이버가
SensorDataQoS로 발행하는데 소비자가 기본 QoS로 구독하는 것이 ROS 2에서 가장 흔한 사고다(1.5.4의 ①). 센서 토픽을 구독할 때는 먼저ros2 topic info -v로 발행자 QoS를 확인하는 습관을 들이라.
1.6.3.2 제어 명령 (/cmd_vel, 관절 명령)
auto qos = rclcpp::QoS(rclcpp::KeepLast(1))
.reliable()
.durability_volatile()
.lifespan(std::chrono::milliseconds(200));
근거:
depth=1— 최신 명령만 의미가 있다. 큐에 명령이 쌓이는 것은 위험하다. 정지 명령 앞에 전진 명령 5개가 대기하는 상황을 만들면 안 된다.RELIABLE— 명령은 작고 드물다. 재전송 비용이 낮고, 놓치면 위험하다.lifespan— 오래된 명령의 뒤늦은 실행을 원천 차단. 통신이 200 ms 끊겼다 복구될 때 옛 명령이 살아나면 안 된다.
견해: 명령 토픽에는
lifespan을 거의 항상 두는 것이 좋다. 이것은 QoS로 표현한 안전 장치다. 다만 워치독(일정 시간 명령이 없으면 정지)은 QoS가 아니라 수신 노드가 구현해야 한다(1.4.8). QoS는 "불량 데이터가 오는 것"을 막지만 "데이터가 안 오는 것"에 대응하지는 않는다.
1.6.3.3 정적·준정적 데이터 (/map, /robot_description, /tf_static)
auto qos = rclcpp::QoS(rclcpp::KeepLast(1))
.reliable()
.transient_local();
근거: 늦게 기동된 노드도 반드시 받아야 한다. 발행은 드물고 크기는 클 수 있다.
숨은 비용:
| 비용 | 설명 |
|---|---|
| 메모리 | 발행자가 depth만큼 계속 들고 있다. 큰 지도면 그만큼 상주 |
| 신규 연결 부하 | 구독자가 연결될 때마다 전체를 다시 보낸다 — 노드가 많으면 부팅 시 트래픽 급증 |
| 구독자 제약 | 구독자도 TRANSIENT_LOCAL이어야 한다(1.5.4의 ②) |
| 재시작 시 소멸 | 발행자 프로세스가 종료되면 보관 데이터도 사라진다. "영속"이 아니다 |
마지막 항목이 자주 오해된다. TRANSIENT_LOCAL은 발행자의 수명 안에서만 유효하다. 실제 영속성이 필요하면 파일·데이터베이스로 저장하고 재시작 시 다시 발행해야 한다.
함정:
/tf_static은TRANSIENT_LOCAL이며 여러 발행자가 각자 보관한다. 정적 변환을 여러 노드가 나눠 발행하면 늦게 기동된 구독자는 전부 받지만, 같은 프레임 쌍을 두 노드가 발행하면 충돌한다. tf 쪽 함정은 5.2에서 다룬다.
1.6.3.4 상태·오도메트리 (/odom, /joint_states)
auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable(); // 또는 best_effort
판단이 갈리는 영역이다.
RELIABLE근거: 오도메트리는 누적 값이라 연속성이 중요할 수 있다. 소비자가 적분·필터링을 한다면 빠진 샘플이 오차가 된다.BEST_EFFORT근거: 고빈도(500 Hz 관절 상태)면 재전송 비용이 크고, 최신 값만 쓰는 소비자가 많다.
견해: 주기 100 Hz 이하 + 유선 =
RELIABLE, 그 이상 또는 무선 =BEST_EFFORT를 출발점으로 삼되, 소비자가 무엇을 하는지로 최종 결정한다.robot_localization같은 융합 노드(7.6)는 샘플 유실에 상대적으로 강건하게 설계되어 있으므로BEST_EFFORT가 무리가 아니다.
1.6.3.5 진단·이벤트 (/diagnostics, 알림)
auto qos = rclcpp::QoS(rclcpp::KeepLast(50)).reliable();
저빈도·소형이며 놓치면 안 된다. depth를 넉넉히 — 소비자(진단 집계기)가 잠깐 멈춰도 이벤트를 잃지 않도록. TRANSIENT_LOCAL은 대개 불필요하다(과거 진단은 로그·bag에서 본다).
1.6.3.6 요약 표
| 데이터 | Reliability | Durability | History | 추가 |
|---|---|---|---|---|
| 고빈도 센서 | BEST_EFFORT | VOLATILE | KEEP_LAST(5) | deadline |
| 제어 명령 | RELIABLE | VOLATILE | KEEP_LAST(1) | lifespan |
| 정적 데이터 | RELIABLE | TRANSIENT_LOCAL | KEEP_LAST(1) | — |
| 상태·오도메트리 | 상황에 따라 | VOLATILE | KEEP_LAST(10) | — |
| 진단·이벤트 | RELIABLE | VOLATILE | KEEP_LAST(50) | — |
로그(/rosout) | RELIABLE | TRANSIENT_LOCAL | KEEP_LAST(1000) | 내장 |
1.6.4 depth를 계산으로 정하기
"10이면 되겠지"를 대체하는 방법이다. depth의 의미는 "소비자가 얼마나 뒤처져도 데이터를 안 잃는가" 이며, 이는 곧 허용 지연이다.
발행 주기 $T_{\text{pub}}$, 소비자가 뒤처질 수 있는 최대 시간 $\Delta t_{\text{lag}}$일 때:
$$\text{depth} \geq \left\lceil \frac{\Delta t_{\text{lag}}}{T_{\text{pub}}} \right\rceil$$
예: 30 Hz 카메라($T_{\text{pub}} \approx 33$ ms), 처리 노드가 GC·스케줄링으로 최대 100 ms 밀릴 수 있다면 $\text{depth} \geq 4$. 여유를 둬 5~6.
반대 방향의 제약 — depth가 만드는 지연:
$$\text{지연}_{\max} \approx \text{depth} \times T_{\text{pub}}$$
depth 100인 30 Hz 토픽은 최악의 경우 3.3초 묵은 데이터를 콜백에 넘긴다. 제어 루프에서 이것은 유실보다 해롭다 — 틀린 데이터로 정상 동작하는 것처럼 보이기 때문이다.
메모리 제약:
$$M \approx \text{depth} \times S_{\text{msg}} \times (\text{발행자} + \text{구독자 수})$$
1920×1080 RGB 이미지(약 6.2 MB)에 depth 10이면 엔드포인트마다 62 MB다. 임베디드 보드에서 이것만으로 메모리가 무너진다.
견해: 의심스러우면 작게 설정하라. 큰 depth는 문제를 숨긴다 — 유실이 안 보이니 처리가 느리다는 사실도 안 보인다. 작은 depth는 문제를 즉시 드러낸다(
message_lost이벤트 — 1.4.6). 드러난 문제가 숨은 문제보다 낫다.
1.6.5 무선·손실 링크 — RELIABLE의 역설
유선에서 잘 동작하던 시스템이 무선에서 무너지는 전형적 경로다.
대처 순서:
- 보내는 양을 줄인다. 이것이 가장 효과가 크다 — 이미지 압축(7.2), 다운샘플링, 발행률 감소, 원격에 필요한 토픽만 선별.
BEST_EFFORT로 내린다. 대용량 스트림은 무선에서RELIABLE을 고집할 이유가 거의 없다.lifespan으로 오래된 것을 버린다. 재전송 대기열이 오래된 데이터로 차는 것을 막는다.- depth를 줄인다. 정체 시 메모리·지연 폭증을 제한.
- 그래도 필요하면 링크를 분리한다 — 제어(소형·
RELIABLE)와 데이터(대형·BEST_EFFORT)를 다른 인터페이스·다른 채널로. - 아키텍처를 바꾼다 — 원격에서 전체 그래프를 보려 하지 말고, 로봇에서 요약·압축해 보내거나 Discovery Server·zenoh를 검토(3.4·3.10).
함정: 멀티캐스트가 무선에서 신뢰할 수 없다는 점이 별도 문제로 겹친다. 많은 Wi-Fi AP가 멀티캐스트를 최저 속도로 보내거나 원천적으로 막는다. 디스커버리 자체가 안 되는 경우가 여기서 나온다(3.3·3.9). QoS를 조정하기 전에
ros2 multicast send·receive로 도달성부터 확인하라(0.7.7).
1.6.6 시스템 차원의 QoS 관리
노드 수십 개 규모가 되면 QoS 일관성이 개인의 기억력에 달려서는 안 된다.
① 공유 헤더로 중앙화
// my_robot_qos/qos_profiles.hpp
namespace my_robot::qos {
inline rclcpp::QoS sensor_stream() {
return rclcpp::SensorDataQoS().deadline(std::chrono::milliseconds(150));
}
inline rclcpp::QoS command() {
return rclcpp::QoS(rclcpp::KeepLast(1)).reliable()
.lifespan(std::chrono::milliseconds(200));
}
inline rclcpp::QoS static_data() {
return rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local();
}
}
발행·구독 양쪽이 같은 함수를 호출하면 불일치가 구조적으로 사라진다. 팀 규모에서 가장 효과가 큰 조치다.
② 파라미터 오버라이드를 열어 둔다 — QosOverridingOptions(1.5.7). 현장 튜닝 여지.
③ 문서화 — 토픽 계약 표
| 토픽 | 타입 | 주기 | QoS | 발행 | 소비 |
|---|---|---|---|---|---|
/scan | sensor_msgs/msg/LaserScan | 10 Hz | sensor_stream | lidar_driver | costmap, slam |
/cmd_vel | geometry_msgs/msg/Twist | 20 Hz | command | nav2 | base_driver |
/map | nav_msgs/msg/OccupancyGrid | 이벤트 | static_data | map_server | costmap, rviz |
이 표는 QoS 문서이면서 인터페이스 계약 문서이자 대역 예산 표(1.3.9)다. 한 장으로 세 가지를 한다.
④ 부팅 검증 — 시스템 기동 후 주요 토픽의 QoS 정합을 자동 확인한다.
for t in /scan /cmd_vel /map; do
echo "=== $t ==="; ros2 topic info -v "$t"
done
이것을 통합 테스트에 넣으면 QoS 회귀를 CI에서 검출할 수 있다(14.6).
1.6.7 서비스·액션의 QoS
토픽만 QoS가 있는 것이 아니다.
- 서비스는 요청·응답 각각의 QoS를 가진다. 기본은
ServicesQoS()(RELIABLE).TRANSIENT_LOCAL은 서비스에 쓰면 안 된다 — 과거 요청이 새 서버에 재전달되는 비정상적인 상황이 생긴다. - 액션은 내부적으로 서비스 3개 + 토픽 2개이며(1.8), 각각의 QoS를 설정할 수 있다. 특히 피드백 토픽은 고빈도일 수 있어 별도 고려가 필요하다.
/parameter_events(1.9)는 depth가 크게 설정돼 있다 — 파라미터 변경을 놓치면 상태 불일치가 생기기 때문이다.
정직: 서비스·액션 QoS 설정 API의 정확한 형태와 기본값은 버전에 따라 다를 수 있다
(확인 필요). 1.7·1.8에서 각각 다룬다.
1.6.8 결정 절차 — 처음 보는 토픽에 QoS 정하기
기존 생태계 노드와 연결할 때는 ①이 항상 먼저다. Nav2·MoveIt 2·ros2_control이 정한 QoS를 바꿀 수 없는 경우가 많으므로, 내 노드가 맞추는 편이 빠르다. 상대가 무엇을 쓰는지는 소스나 문서보다 ros2 topic info -v가 정본이다.
1.6.11 표준 프로파일을 런타임에서 확인했다 — 실측
이 장과 1.5.5의 프로파일 표는 헤더를 읽어 만든 것이다. 헤더가 맞아도 실제로 생성된 객체가 그 값인지는 별개이므로, rclcpp로 프로파일을 만들어 값을 출력해 봤다(실행 확인, Jazzy).
1.6.11.1 결과 — 표가 전부 맞았고, 하나가 빠져 있었다
| 프로파일 | 신뢰성 | 내구성 | 히스토리 | depth |
|---|---|---|---|---|
QoS(10) (기본) | RELIABLE | VOLATILE | KEEP_LAST | 10 |
SensorDataQoS | BEST_EFFORT | VOLATILE | KEEP_LAST | 5 |
ServicesQoS | RELIABLE | VOLATILE | KEEP_LAST | 10 |
ParametersQoS | RELIABLE | VOLATILE | KEEP_LAST | 1000 |
ParameterEventsQoS | RELIABLE | VOLATILE | KEEP_LAST | 1000 |
RosoutQoS | RELIABLE | TRANSIENT_LOCAL | KEEP_LAST | 1000 |
ClockQoS | BEST_EFFORT | VOLATILE | KEEP_LAST | 1 |
SystemDefaultsQoS | SYSTEM_DEFAULT | SYSTEM_DEFAULT | KEEP_LAST | 0 |
QoS(KeepAll()) | RELIABLE | VOLATILE | KEEP_ALL | 0 |
표의 값은 전부 일치했다. 다만 ClockQoS가 두 표(1.5.5·A.2.4) 어디에도 없었다 — 이번에 넣었다.
1.6.11.2 /clock이 BEST_EFFORT + depth 1이라는 뜻
시뮬레이션을 쓰는 독자에게 이 한 줄이 크다(1.10.3).
BEST_EFFORT다 →/clock메시지는 유실되어도 재전송되지 않는다. 부하가 걸리면 시간이 튀거나 잠깐 멈춘 것처럼 보일 수 있다.- depth가 1이다 → 이력이 없다. 늦게 참여한 구독자는 다음 발행을 기다려야 하고, 발행이 멈춘 상태라면 영원히 시각을 못 받는다.
use_sim_time=true인데 노드가 초기화에서 멈춘 것처럼 보이는 증상(1.10.3의 "시간이 0에 멈춤")이 여기에 닿는다. TRANSIENT_LOCAL이 아니다 → "늦게 시작해도 마지막 시각은 받겠지"라는 기대는 성립하지 않는다.
함정:
/clock발행자를RELIABLE로 바꾸면 구독이 매칭되지 않을 수 있다. 구독 측(노드 내부의 시계)이BEST_EFFORT로 만들어지므로, 발행자만RELIABLE이면 호환은 되지만(1.5.3) 반대로 발행자를BEST_EFFORT로 두고 구독자를RELIABLE로 바꾸면 연결되지 않는다.NodeOptions::clock_qos()로 바꿀 때(2.10) 양쪽을 함께 보라.
1.6.11.3 .deadline() 같은 체이닝은 나머지를 보존한다
1.6.6은 rclcpp::SensorDataQoS().deadline(150ms) 형태를 쓴다. 덧붙인 정책만 바뀌고 기반 프로파일이 유지되는지 확인했다.
| 신뢰성 | 히스토리 | depth | |
|---|---|---|---|
SensorDataQoS() | BEST_EFFORT | KEEP_LAST | 5 |
SensorDataQoS().deadline(150ms) | BEST_EFFORT | KEEP_LAST | 5 |
유지된다. 즉 "표준 프로파일에서 출발해 필요한 정책만 덧붙이는" 이 장의 방식이 안전하다.
견해: 그래서 QoS는 처음부터 직접 조립하지 말고 표준 프로파일에서 출발하라.
QoS(1).best_effort().durability_volatile()처럼 전부 적는 방식은 빠뜨린 정책이 기본값으로 남는 것을 눈치채기 어렵다.SensorDataQoS()로 시작하면 의도가 이름에 남는다.
1.6.11.4 확인하지 못한 것
lifespan·liveliness의 실제 발화는 측정하지 않았다.RosoutQoS의 lifespan 10초는 헤더로 확인했지만(1.5.5), 10초 뒤 실제로 이력에서 사라지는지는 별개다(확인 필요).BestAvailableQoS는 이 실험에 포함하지 않았다(확인 필요).
1.6.12 system_default는 보고값과 동작이 어긋난다 — 실측
1.6.11이 프로파일 객체의 값을 확인했다면, 이번에는 그 객체로 만든 엔드포인트가 실제로 어떻게 실행되는지를 봤다. 한 프로파일에서 진단 명령이 거짓말을 한다(실행 확인, Jazzy · rmw_fastrtps_cpp).
1.6.12.1 늦게 참여한 구독자로 갈랐다
발행자가 메시지 3개를 먼저 내보내고 3초 뒤에 구독자를 추가했다. TRANSIENT_LOCAL이면 받아야 하고 VOLATILE이면 못 받는다 — 동작으로 내구성을 판정하는 시험이다.
| 발행자 QoS | 구독자 QoS | 늦은 구독자 수신 |
|---|---|---|
transient_local (대조군) | transient_local | old-0·old-1·old-2 전부 |
system_default | system_default | 없음 |
C++ transient_local → Python 구독 (대조군) | transient_local | 전부 |
C++ SystemDefaultsQoS → Python 구독 | system_default | 없음 |
대조군이 받았으므로 시험 자체는 유효하다. system_default는 늦은 구독자에게 아무것도 주지 않는다 — 즉 동작은 VOLATILE이다.
1.6.12.2 그런데 진단 명령은 TRANSIENT_LOCAL이라고 말한다
같은 두 발행자를 ros2 topic info -v와 get_publishers_info_by_topic()으로 조회했다.
대조군(transient_local) | system_default | |
|---|---|---|
durability | 1 (= TRANSIENT_LOCAL) | 1 (= TRANSIENT_LOCAL) |
history | 3 (= UNKNOWN) | 3 (= UNKNOWN) |
depth | 0 | 0 |
reliability | 1 (= RELIABLE) | 1 (= RELIABLE) |
보고값이 완전히 같다. 그런데 동작은 정반대다.
이것이 이 절의 핵심이다 —
system_default엔드포인트에서는ros2 topic info -v의 QoS 줄을 믿을 수 없다. 값이 같은데 동작이 갈리므로, "보고된 QoS를 비교해 호환성을 판정한다"는 평소의 방법(1.6.3.1·11.1.4)이 이 프로파일에서만 성립하지 않는다.
깊이도 함께 못 믿는다 — 대조군은 depth=10으로 만들었는데 보고값은 0이고 히스토리는 UNKNOWN이다. rmw_fastrtps_cpp가 엔드포인트 정보에 히스토리·깊이를 싣지 않는다고 보는 것이 자연스럽다(1.6.11에서 객체의 값은 정확했다는 점과 대비된다 — 객체는 정확하고 엔드포인트 보고가 부정확하다).
1.6.12.3 원인은 확인하지 못했다
정직하게 적는다 — 왜 TRANSIENT_LOCAL로 보고되는지는 밝히지 못했다. 가능한 설명이 둘인데 이 실험으로는 가르지 못한다(확인 필요).
- (가) 보고 경로의 결함 — rmw가
SYSTEM_DEFAULT(열거값0)를 엔드포인트 정보로 옮기는 과정에서 값이 어긋난다. - (나) 실제로
TRANSIENT_LOCAL로 만들어지되 유지되는 이력이 없다 — DDS 쪽 기본 히스토리 깊이 때문에 보관이 일어나지 않는다.
어느 쪽이든 독자에게 필요한 결론은 같다: system_default의 내구성은 보고값으로 판단하지 말고 동작으로 확인하라. 위의 늦은 구독자 시험이 그 방법이며, 5줄이면 된다.
견해:
system_default(=SystemDefaultsQoS·qos_profile_system_default)를 실무 토픽에 쓰지 마라. ① 값이 DDS 벤더·설정 파일에 따라 달라지고(1.7), ② 진단 명령이 알려 주지 못하며, ③ 이름과 달리 "ROS의 기본"이 아니다 — ROS의 기본은QoS(10)(RELIABLE·VOLATILE)이다. 벤더 XML로 QoS를 일괄 지정하려는 의도가 있을 때만 쓰고, 그때도 위 시험으로 결과를 확인하라.
1.6.9 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 센서 QoS 불일치 | 조용한 무데이터 | topic info -v 먼저(1.6.3.1) |
| 2 | 명령 토픽 depth를 크게 | 정지 명령이 큐 뒤에 대기 | depth=1 + lifespan |
| 3 | TRANSIENT_LOCAL을 영속으로 오해 | 발행자 재시작 후 소멸 | 파일·DB로 영속화 |
| 4 | 큰 지도에 TRANSIENT_LOCAL + 노드 다수 | 부팅 시 트래픽·메모리 급증 | 구독자 수 통제, 필요한 곳만 |
| 5 | 이미지에 depth 10 | 수십 MB 상주 | 크기×depth 계산(1.6.4) |
| 6 | 무선에서 대용량 RELIABLE | 꼬리 지연 100~200배(실측) | 양 줄이기 → BEST_EFFORT(순서 중요, 3.9.13.5) |
| 7 | 노드마다 QoS를 직접 작성함 | 시간이 지나며 어긋남 | 공유 헤더(1.6.6) |
| 8 | 서비스에 TRANSIENT_LOCAL | 과거 요청 재전달 | 쓰지 말 것 |
| 9 | QoS로 처리 지연 해결 시도 | 안 풀림 | executor 층(2.3·2.4) |
| 10 | 멀티캐스트 문제를 QoS로 오진 | 시간 낭비 | ros2 multicast 먼저(1.6.5) |
| 11 | deadline을 발행 주기와 같게 | 지터마다 이벤트 폭주 | 1.5~2배 여유 |
| 12 | QoS 변경을 재빌드로만 | 현장 대응 불가 | QosOverridingOptions |
| 13 | /clock이 이력을 준다고 기대 | 늦게 기동된 노드가 시각을 못 받음 | ClockQoS는 depth 1·VOLATILE(1.6.11.2) |
| 14 | QoS를 직접 전부 조립 | 빠뜨린 정책이 조용히 기본값 | 표준 프로파일에서 출발(1.6.11.3) |
| 15 | system_default를 "ROS 기본"으로 오해 | 벤더 설정에 좌우, 이식성 없음 | ROS 기본은 QoS(10) — 명시적 프로파일을 쓰라(1.6.12) |
| 16 | topic info -v의 내구성을 믿고 판정 | system_default에서 TRANSIENT_LOCAL로 나오는데 동작은 VOLATILE | 늦은 구독자 시험으로 동작 확인(1.6.12.1) |
1.6.10 체크포인트
- 새 토픽의 QoS를 정하는 네 가지 질문을 순서대로 말할 수 있는가.
- "중요한 데이터 =
RELIABLE"이 왜 틀린 추론인지, 레이저 스캔을 예로 설명할 수 있는가. - 제어 명령 토픽에
depth=1과lifespan을 두는 근거를 각각 말할 수 있는가. QoS가 대신해 주지 못하는 안전 장치는 무엇인가. TRANSIENT_LOCAL의 네 가지 숨은 비용을 들 수 있는가. 그중 가장 자주 오해되는 것은?- 30 Hz 토픽에서 소비자가 최대 150 ms 밀릴 수 있다면 depth를 얼마로 설정하겠는가. 그 depth가 만드는 최악 지연과 메모리는 얼마인가.
- "의심스러우면 depth를 작게"라는 권고의 근거를 말할 수 있는가.
- 무선에서
RELIABLE이 악순환을 만드는 경로를 그리고, 대처를 효과 큰 순서로 나열할 수 있는가. - 팀 규모에서 QoS 불일치를 구조적으로 없애는 방법 네 가지를 들 수 있는가.
- 서비스에
TRANSIENT_LOCAL을 쓰면 안 되는 이유는 무엇인가. - 기존 생태계 노드와 연결할 때 QoS 결정의 첫 단계는 무엇인가.
/clock의 기본 QoS 세 값은 무엇이며, 그것이 시뮬 시간 문제에 어떻게 나타나는가(1.6.11.2).- 표준 프로파일에
.deadline()을 붙이면 나머지 정책은 어떻게 되는가(1.6.11.3). system_default로 만든 발행자의 내구성을ros2 topic info -v로 판정할 수 있는가. 왜 안 되며, 대신 무엇으로 확인하는가(1.6.12).
여기까지가 QoS다. 다음 장 1.7부터는 토픽이 아닌 통신 패턴 — 서비스의 내부 구조와, ROS 2에서 가장 악명 높은 실수인 동기 호출 데드락 — 으로 넘어간다.