제3부 미들웨어: DDS·RTPS·RMW
3.3 디스커버리 — SPDP·SEDP와 그 비용
3.3.1 이 장에서 배우는 것 / 선수 지식
ROS 1에는 마스터가 있었다. ROS 2에는 없다. 그래서 노드들은 서로를 스스로 찾아야 하고, 그 과정이 디스커버리다. 이 장은 ROS 2에서 가장 자주 문제를 일으키는 층을 다룬다 — "노드가 안 보인다", "연결이 오래 걸린다", "노드를 늘렸더니 전체가 느려졌다", "종료된 노드가 남아 있다"의 답이 전부 여기 있다.
그리고 이 장은 ROS 2의 확장성 한계를 정면으로 다룬다. 디스커버리 비용은 노드 수의 제곱에 비례하며, 이것이 대규모 시스템에서 벽이 된다. 그 벽을 넘는 방법이 다음 장들(3.4 Discovery Server, 3.10 zenoh)의 주제다.
배우는 것:
- SPDP — 참가자를 찾는 단계. 멀티캐스트·주기적 알림.
- SEDP — 엔드포인트(발행자·구독자)를 맞추는 단계. 여기서 QoS가 교환된다.
- 디스커버리 비용의 수학 — 왜 $O(N^2)$인가, 실제로 얼마나 되는가.
- 타이밍 — 왜 즉시 안 보이는가, 얼마나 기다려야 하는가.
- 참가자 매핑 — 노드마다 참가자 vs 프로세스마다 참가자, 이 차이가 전부를 바꾼다.
- 디스커버리 폭풍 — 언제 발생하고 어떻게 진단하는가.
- Jazzy의 디스커버리 범위 제어 —
ROS_AUTOMATIC_DISCOVERY_RANGE·ROS_STATIC_PEERS. - 완화 수단 개관 — 무엇을 언제 쓰는가.
- 참가자 규모 실측(3.3.12) — 디스커버리 시간의 실제 지배 요인과, 먼저 부딪히는
/dev/shm벽.
선수 지식: 3.1(DDS 개념·도메인), 3.2(RTPS·포트), 0.7.3(데몬과 유령 노드), 1.1.8(그래프 질의의 한계).
정직: SPDP·SEDP 동작은 RTPS 명세에 근거하나 주기·타임아웃 기본값은 벤더마다 다르고 설정 가능하다. 아래 수치 예시는 비용 구조를 보이기 위한 계산이며 특정 시스템의 측정값이 아니다(BK-03). 노드↔참가자 매핑은 벤더·버전 의존이다 — Jazzy 기본 RMW에서는 프로세스(컨텍스트)당 참가자 하나임을 실측으로 확인했다(3.3.5). 이 매핑이 비용을 좌우하므로 다른 RMW를 쓴다면 자신의 환경에서 확인하기 바란다.
3.3.2 두 단계 디스커버리
두 단계로 나눈 이유: 참가자를 찾는 것은 누구인지 모르는 상대를 찾는 일이라 멀티캐스트가 필요하다. 반면 엔드포인트 정보는 이미 아는 상대와 주고받으므로 유니캐스트로 충분하고, 빠뜨리면 안 되므로 신뢰성이 필요하다.
| 단계 | 전송 | 신뢰성 | 주기 | 내용 |
|---|---|---|---|---|
| SPDP | 멀티캐스트 | BEST_EFFORT | 주기적 반복 | 참가자 GUID, 유니캐스트 주소, lease duration, 벤더 |
| SEDP | 유니캐스트 | RELIABLE | 변경 시 | 엔드포인트별 토픽 이름·타입·RxO 판정에 쓰이는 QoS(history/depth 제외) |
SPDP가 주기적으로 반복되는 이유가 중요하다. BEST_EFFORT이므로 유실될 수 있고, 나중에 켜진 노드도 알아야 하기 때문이다. 즉 SPDP 트래픽은 시스템이 조용해도 계속 흐른다 — 이것이 상시 부하가 된다.
SEDP에서 QoS가 교환된다는 사실이 실무적으로 중요하다. ros2 topic info -v가 상대 QoS를 보여줄 수 있는 이유가 이것이고(0.7.5), QoS 불일치가 연결 시도 단계에서 판정되는 이유이기도 하다(1.5.4).
다만 모든 정책이 교환되는 것은 아니다. 교환되는 것은 RxO 판정에 참여하는 정책이며, History·depth는 상대에게 알릴 필요가 없는 로컬 자원 정책이라 빠진다. 그래서 Jazzy·rmw_fastrtps_cpp에서 ros2 topic info -v는 상대 엔드포인트의 History (Depth)를 UNKNOWN으로 표시한다(실행 확인). "큐가 얼마나 깊은지는 상대가 알 수 없다" 는 것이 유실 진단에서 중요하다(1.4.4).
3.3.3 왜 즉시 안 보이는가 — 타이밍
이 과정에 시간이 걸린다. 얼마나 걸리는지는 SPDP 주기, 네트워크 지연, 참가자 수, 엔드포인트 수에 달렸고 수백 밀리초에서 수 초까지 될 수 있다(확인 필요: 환경 의존).
여기서 나오는 현상들:
| 현상 | 원인 | 관련 장 |
|---|---|---|
| 시작 직후 발행이 아무에게도 안 감 | 아직 매칭 전 | 1.4.7 |
ros2 topic list에 방금 기동된 노드가 없음 | 데몬 캐시 + 디스커버리 진행 중 | 0.7.3 |
| 첫 서비스 호출이 실패 | 서버가 아직 발견 안 됨 | 1.7.5 |
| 노드가 많을수록 시작이 느림 | 매칭 조합 수 증가 | 3.3.4 |
| 머신마다 보이는 것이 다름 | 디스커버리는 점진적·비대칭 | 0.7.6 |
올바른 대처는 "기다리는 것"이 아니라 "기다림을 설계에 넣는 것"이다.
// 서비스: 명시적 대기 + 타임아웃 (1.7.5)
if (!client_->wait_for_service(std::chrono::seconds(5))) { … }
// 액션
if (!action_client_->wait_for_action_server(std::chrono::seconds(5))) { … }
// 토픽: TRANSIENT_LOCAL로 늦게 참여하는 구독자도 받게 (1.4.7)
auto qos = rclcpp::QoS(1).transient_local().reliable();
종료되는 쪽도 즉시가 아니다. 참가자가 사라진 것은 lease duration이 지나야 판정된다. 정상 종료하면 참가자 소멸 알림을 보내지만, SIGKILL·전원 차단·네트워크 단절에서는 알림이 없고 lease 만료를 기다려야 한다. 이것이 ros2 node list에 유령 노드가 남는 이유다(0.7.3).
3.3.4 비용의 수학 — $O(N^2)$
이것이 이 장의 핵심이다.
참가자가 $N$개일 때, 모든 참가자가 모든 참가자를 알아야 하므로 SPDP 관계는
$$\binom{N}{2} = \frac{N(N-1)}{2} = O(N^2)$$
SEDP는 더 불리하다. 참가자마다 엔드포인트가 $E$개 있으면, 매칭 검사는 대략
$$O(N^2 \cdot E^2)$$
에 비례하는 작업이 된다(모든 발행자 × 모든 구독자 조합에 대해 토픽 이름·타입·QoS 비교).
구체적으로 보자. 노드 $n$개, 노드마다 엔드포인트 $e$개, 노드마다 참가자를 만드는 구현이라면:
| 노드 수 | 참가자 쌍 | 엔드포인트 총수(e=10) | 매칭 검사 규모 |
|---|---|---|---|
| 10 | 45 | 100 | 작다 |
| 50 | 1,225 | 500 | 눈에 띄기 시작 |
| 100 | 4,950 | 1,000 | 부담 |
| 200 | 19,900 | 2,000 | 심각 |
| 500 | 124,750 | 5,000 | 사실상 불가 |
중요 — 이 표의 $N$을 "노드 수"로 읽지 말라(실행 확인). 위 표는 명시한 대로 "노드마다 참가자를 만드는 구현" 을 전제한 것인데, Jazzy 기본 RMW는 그 구현이 아니다 — 한 프로세스의 노드들이 참가자 하나를 공유한다(3.3.5에서 실측). 따라서 Jazzy에서 $N$은 노드 수가 아니라 참가자 수, 즉 대략 프로세스 수다.
차이가 크다. 노드 100개를 프로세스 100개로 실행하면 참가자 쌍이 4,950이지만, 같은 노드 100개를 컨테이너 5개에 나눠 담으면 $N=5$가 되어 참가자 쌍은 10개다. 표의 "부담" 칸이 "작다" 칸으로 내려온다.
이것이 컴포지션(1.13)의 두 번째 논거다. 복사·직렬화 제거(2.7)는 데이터 경로의 이득이고, 참가자 감소는 디스커버리 경로의 이득이다. 후자는 대규모 시스템에서 오히려 더 결정적일 수 있다 — 3.4(Discovery Server)를 도입하기 전에 먼저 프로세스 수를 줄이는 것이 값싼 대책인 이유다.
메모리도 함께 는다. 각 참가자가 다른 모든 참가자와 엔드포인트의 정보를 캐시해야 한다.
$$M_{\text{디스커버리}} \approx N \cdot (N \cdot c_p + N \cdot E \cdot c_e)$$
$c_p$는 참가자 정보 크기, $c_e$는 엔드포인트 정보 크기다. $N^2$ 항이 지배한다.
상시 트래픽: SPDP가 주기 $T_{\text{spdp}}$로 반복되므로
$$B_{\text{spdp}} \approx \frac{N \cdot S_{\text{spdp}}}{T_{\text{spdp}}} \quad \text{(멀티캐스트라면 각자 1회 발송)}$$
이것은 $O(N)$이지만, 수신 측에서는 $N$개를 모두 처리해야 하므로 CPU 부하는 $O(N^2)$로 느껴진다.
견해: "ROS 2는 몇 개 노드까지 되는가"에 고정된 답은 없다. 노드당 엔드포인트 수, 참가자 매핑, 네트워크, 하드웨어에 따라 크게 달라진다. 다만 수십 개까지는 대개 문제없고, 100개를 넘어가면 디스커버리를 의식적으로 설계해야 한다는 것이 일반적 경험칙이다. 그 시점에 필요한 것이 3.4(Discovery Server)·3.10(zenoh)·도메인 분리(1.1.7)다.
3.3.5 참가자 매핑 — 비용을 좌우하는 한 가지
같은 노드 수라도 참가자 수가 다르면 비용이 완전히 달라진다.
| 축 | 노드=참가자 | 프로세스=참가자 |
|---|---|---|
| SPDP 비용 | 노드 수에 비례 | 프로세스 수에 비례 |
| 포트 소비 | 노드마다(3.2.6) | 프로세스마다 |
| 메모리 | 큼 | 작음 |
| 격리 | 노드별 | 프로세스별 |
실행 확인(2026-08-16) — Jazzy 기본 RMW는 오른쪽(프로세스=참가자)이다. 위에 적어 둔 확인 방법을 그대로 썼다. 한 프로세스에 노드 4개를 실행하고
ros2 topic info -v로 엔드포인트 GID를 비교하니 네 노드의 GUID prefix가 전부 같았다 — 참가자 하나를 공유한다는 뜻이다(2.1.4).이것이 컴포지션(1.13)의 이득을 하나 더 만든다. 컨테이너에 노드를 묶으면 복사·직렬화만 줄어드는 것이 아니라 참가자 수 자체가 줄어 SPDP 비용과 포트 소비가 함께 준다. 노드 10개를 컨테이너 하나에 넣으면 위 표의 왼쪽이 오른쪽이 된다.
다른 벤더·버전은 다를 수 있으므로
(확인 필요), 대규모 시스템이라면 자신의 RMW에서 같은 방법으로 확인하라 — 패킷 캡처로 GuidPrefix 수를 세거나(3.2.3),ss -ulnp로 프로세스별 포트 수를 봐도 된다.
여기서 컴포지션(1.13)의 또 다른 이점이 드러난다. 노드 10개를 한 컨테이너에 넣으면, 참가자가 프로세스 단위로 만들어지는 구현에서는 디스커버리 비용이 10분의 1이 된다. 컴포지션은 통신 비용뿐 아니라 디스커버리 비용도 줄인다(1.13.2의 비교표에 이 축이 있다).
견해: 대규모 시스템에서 컴포지션은 성능 최적화가 아니라 확장성 요건이 된다. 노드 200개를 각각 프로세스로 실행하는 구성과, 20개 컨테이너에 나눠 담는 구성은 디스커버리 부하가 자릿수로 다르다.
3.3.6 디스커버리 폭풍
증상: 시스템 전체가 느려지고, CPU가 이유 없이 높고, 데이터 토픽 대역보다 디스커버리 트래픽이 크다.
발생 조건:
| 조건 | 설명 |
|---|---|
| 노드 다수 동시 시작 | 부팅 시 모두가 모두에게 알림 |
| 노드가 반복 재시작 | 크래시 루프가 디스커버리를 계속 유발 |
| 엔티티 동적 생성·소멸 | SEDP가 매번 발생(2.2.6과 같은 원인) |
| CLI를 반복 실행 | 실행마다 임시 참가자 생성(0.7.4) |
| 큰 그래프 + 짧은 SPDP 주기 | 상시 부하 |
진단:
# 1. 디스커버리 트래픽 비율 (3.2.8)
sudo tcpdump -i any -w disc.pcap 'udp port 7400' # 도메인 0 디스커버리
# Wireshark에서 전체 RTPS 대비 비율 확인
# 2. 참가자 수 세기 — 서로 다른 GuidPrefix 개수
# Wireshark: Statistics → Conversations, 또는 rtps.guidPrefix 로 그룹화
# 3. 노드·엔티티 수
ros2 node list | wc -l
ros2 topic list --include-hidden-topics | wc -l
# 4. 포트 소비 (참가자 수의 대리 지표)
ss -ulnp | grep -c 74
대처 순서:
- 엔티티를 초기화 때 만들고 유지한다(동적 생성 제거 — 2.2.6).
- 컴포지션으로 프로세스를 줄인다(1.13·3.3.5).
- CLI 반복 실행을 줄인다 — 데몬 재사용(0.7.3).
- 디스커버리 범위를 제한한다(3.3.7).
- 도메인을 분리한다(1.1.7의 ②).
- Discovery Server로 전환한다(3.4).
- zenoh를 검토한다(3.10).
3.3.7 디스커버리 범위 제어 — Jazzy의 환경변수
ROS 2는 디스커버리 범위를 제어하는 환경변수를 제공한다.
| 변수 | 값 | 의미 |
|---|---|---|
ROS_AUTOMATIC_DISCOVERY_RANGE | SUBNET | 같은 서브넷(멀티캐스트) — 일반적 기본 |
LOCALHOST | 같은 머신 안에서만 | |
OFF | 자동 디스커버리 끔(정적 피어만) | |
SYSTEM_DEFAULT | 미들웨어 기본에 위임 | |
ROS_STATIC_PEERS | 192.168.1.10;192.168.1.11 | 명시적으로 지정한 피어와 디스커버리 |
# 개발 머신 격리 — 옆 사람 그래프와 섞이지 않게
export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST
# 특정 로봇하고만
export ROS_AUTOMATIC_DISCOVERY_RANGE=OFF
export ROS_STATIC_PEERS="192.168.1.10"
정직: 이 변수들은
ROS_LOCALHOST_ONLY를 대체한다. Jazzy에서ROS_LOCALHOST_ONLY는 폐기(deprecated)되었으나 켜면 여전히 동작하며, 그때 위 두 변수는 무시된다 — 실행하면rcl이 그 취지의 경고 두 줄을 낸다(실행 확인). 값 넷(OFF·LOCALHOST·SUBNET·SYSTEM_DEFAULT)도rmw의 열거형과 일치함을 확인했다. 다만 RMW별 실제 격리 동작까지 확인한 것은 아니다 — 격리를 가정했는데 안 되면 보안·간섭 문제가 되므로 자신의 환경에서 반드시 확인하라.
LOCALHOST가 개발 환경에서 특히 유용하다. 여러 개발자가 같은 네트워크에서 작업할 때, 서로의 노드가 섞여 보이는 혼란(그리고 서로의 로봇에 명령이 가는 사고)을 막는다.
함정: 디스커버리 범위 제한은 보안 수단이 아니다(3.1.4와 같은 이유). 환경변수는 각 프로세스가 스스로 지키는 것일 뿐, 다른 누군가가 다르게 설정하면 그만이다. 실제 격리는 네트워크·SROS2로 해야 한다(13.1·13.2).
ROS_STATIC_PEERS의 가치: 멀티캐스트가 안 되는 환경(일부 클라우드·컨테이너·무선 — 3.9)에서 유니캐스트만으로 디스커버리를 성립시킬 수 있다. 노드 수가 적고 주소가 고정된 시스템에서 실용적이다.
3.3.8 벤더별 디스커버리 튜닝 지점
정확한 설정 방법은 3.7·3.8에서 다루고, 여기서는 어떤 손잡이가 있는지만 정리한다.
| 손잡이 | 효과 | 대가 |
|---|---|---|
| SPDP 알림 주기 | 늘리면 상시 트래픽 감소 | 발견이 느려짐 |
| lease duration | 늘리면 트래픽 감소 | 종료된 노드 감지가 늦어짐 |
| 초기 피어 목록 | 멀티캐스트 없이 디스커버리 | 수동 관리 |
| 멀티캐스트 비활성화 | 유니캐스트 전용 | 피어 목록 필수 |
| SEDP 활성/비활성 | 정적 디스커버리 시 생략 가능 | 설정 복잡도 급증 |
| 참가자 필터 | 관심 없는 참가자 무시 | 벤더 의존 |
"정적 디스커버리(static discovery)" 라는 극단적 선택지도 있다 — 모든 엔드포인트를 XML에 미리 적어 SEDP를 처음부터 생략하는 방식이다.
| 축 | 평가 |
|---|---|
| 디스커버리 트래픽 | 거의 0 |
| 시작 시간 | 매우 빠름 |
| 유연성 | 없음 — 노드 구성이 바뀌면 XML을 고쳐야 |
| 유지보수 | 매우 높은 부담 |
| 적합 | 구성이 완전히 고정된 임베디드·양산 제품 |
견해: 정적 디스커버리는 "구성이 절대 변하지 않는 양산 시스템" 에서만 값을 한다. 개발 중이거나 노드가 동적으로 연결되는 시스템에서는 유지보수 부담이 이득을 압도한다. 대부분의 경우 Discovery Server(3.4)가 더 나은 절충이다 — 트래픽은 크게 줄이면서 동적 구성을 유지한다.
3.3.9 완화 수단 선택
| 수단 | 줄이는 것 | 대가 | 관련 장 |
|---|---|---|---|
| 컴포지션 | 참가자 수 | 격리 상실 | 1.13 |
| 엔티티 정적화 | SEDP 빈도 | 설계 제약 | 2.2.6 |
| 도메인 분리 | 그래프 크기 | 도메인 간 통신 불가 | 1.1.7 |
| 범위 제한 | 불필요한 피어 | 격리 아님 | 3.3.7 |
| 정적 피어 | 멀티캐스트 의존 | 수동 관리 | 3.3.7 |
| Discovery Server | $O(N^2)$ → $O(N)$ | 서버 단일점 | 3.4 |
| 정적 디스커버리 | 거의 전부 | 유연성 전무 | 3.3.8 |
| zenoh | 구조적으로 다름 | 생태계 성숙도 | 3.10 |
가장 값싼 것부터 시도하라. 컴포지션과 엔티티 정적화는 코드 구조 개선이기도 하므로 다른 이득이 따라온다. Discovery Server·zenoh는 인프라 변경이라 검증 비용이 크다.
3.3.12 참가자를 늘려 가며 측정해 봤다 — 실측
3.3.4의 $O(N^2)$는 수학이고, 3.3.5의 "참가자 수가 비용을 좌우한다"는 구조다. 실제로 얼마나 되는지 측정하려고 참가자 수를 5~40으로 늘려 가며 디스커버리 시간과 자원 사용을 봤다(실행 확인, Jazzy, 단일 호스트 컨테이너, rmw_fastrtps_cpp).
3.3.12.1 디스커버리 시간은 이 규모에서 N에 따라 늘지 않았다
관측자 노드가 실행 중인 K개 노드를 전부 볼 때까지의 시간(각 3회).
| 구성 | K=5 | K=40 |
|---|---|---|
| 한 프로세스에 K개 노드 | 198 · 198 · 458 ms | 462 · 466 · 194 ms |
| K개 프로세스에 1노드씩 | 532 · 529 · 535 ms | 226 · 209 · 224 ms |
N과의 상관이 보이지 않는다. 값이 대략 200 ms · 460 ms · 530 ms 부근에 뭉치는 것이 특징인데, 이는 관측자가 SPDP 알림 주기의 어느 지점에서 시작했는가로 갈리는 것이지 참가자 수와는 무관하다.
그러므로 이 규모에서 "디스커버리가 느리다"는 체감은 $N^2$ 때문이 아니다 — 알림 주기를 기다리는 시간이다(3.3.3). $O(N^2)$가 부담이 커지기 시작하는 지점은 이보다 훨씬 큰 규모이며, 이 실험으로는 그 지점을 찾지 못했다
(확인 필요: 수백 참가자 규모).
3.3.12.2 대신 다른 벽에 먼저 부딪혔다 — /dev/shm
측정 중 K=40에서 Fast DDS가 이런 오류를 냈다.
[RTPS_TRANSPORT_SHM Error] Failed to create segment fastrtps_…: No such file or directory
[RTPS_PARTICIPANT Error] Unable to Register SHM Transport.
SHM Transport is not supported in the current platform.
그런데 통신은 계속됐다 — 관측자는 40개를 전부 발견했고 시간도 226 ms였다. 공유 메모리 전송이 조용히 UDP로 격하된 것이다.
원인은 /dev/shm 고갈이었다.
| 참가자 수 | /dev/shm 사용 |
|---|---|
| 5 | 3.5 MB |
| 10 | 6.7 MB |
| 20 | 13 MB |
| 30 | 20 MB |
| 40 | 26 MB |
참가자 하나가 약 0.65 MB, 항목 6개를 쓴다. 그리고 Docker의 기본 /dev/shm은 64 MB다.
3.3.12.3 실제 함정은 누수다 — 종료 방식에 달렸다
세그먼트가 언제 정리되는지를 종료 방식별로 봤다(참가자 10개 기준, 사용량 6.4 MB).
| 종료 방식 | 종료 후 /dev/shm |
|---|---|
정상 종료(main 반환) | 2개 · 8 KB — 정리된다 |
SIGINT | 82개 · 6.4 MB — 그대로 |
SIGTERM | 그대로 |
SIGKILL | 그대로 |
프로세스가 정리 경로를 거치지 않고 종료되면 세그먼트가 남는다.
이것이 장시간 운용에서 조용히 자라는 문제다. 노드가 크래시하거나 감시자가 강제 재시작할 때마다(14.2·14.8) 약 0.65 MB씩 쌓인다. Docker 기본 64 MB면 누적 100회 남짓의 비정상 종료로 가득 찬다. 그리고 가득 차는 순간 오류 로그 몇 줄 외에는 아무 일도 일어나지 않고 공유 메모리 전송만 사라진다 — 기능은 정상인데 성능만 악화된다.
"재시작을 반복하다 보니 어느 순간부터 느려졌다"의 후보로 이것을 기억하라.
확인 방법과 대처:
df -h /dev/shm # 사용률 — 100%면 확정
ls /dev/shm | wc -l # 항목 수
ls /dev/shm | grep fastrtps # 벤더 세그먼트
# 대처 ① 컨테이너에 넉넉히 준다
docker run --shm-size=512m …
# 대처 ② 노드가 모두 내려간 상태에서 청소
rm -f /dev/shm/fastrtps_* /dev/shm/sem.fastrtps_*
함정: 실행 중에 지우지 말라. 실행 중인 참가자의 세그먼트를 지우면 그 참가자의 통신이 깨진다. 모두 내린 뒤 또는 재부팅 시 청소가 안전하다.
견해:
--shm-size를 배포 표준으로 정해 두라(14.8). 64 MB는 노드 몇 개 규모의 개발에는 충분하지만, 컴포넌트 컨테이너 여럿 + 재시작이 있는 운영에서는 얇다. 늘리는 비용이 거의 없으므로 512 MB 정도를 기본으로 두고, 사용량을 진단으로 내보내는 것(6.9)이 실용적이다.
3.3.12.4 확인하지 못한 것
- 수백~수천 참가자 규모는 측정하지 못했다 — 단일 컨테이너에서 40개가 실용적 한계였다. $O(N^2)$가 실제로 아파지는 지점은 미확인이다.
- 다중 호스트에서의 디스커버리 시간은 측정하지 않았다(3.9.12의 제약과 같다).
SIGINT가 정리하지 않은 것은 이 실험 프로그램이spin대신 대기 중이었기 때문일 수 있다 —spin중인 노드가 Ctrl-C를 받는 일반적 경우와 다를 수 있다(확인 필요).SIGKILL에서 남는 것은 확실하다.- Cyclone DDS·zenoh의 SHM 사용은 측정하지 않았다
(확인 필요).
3.3.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | 시작 직후 즉시 통신 가정 | 초기 메시지 유실 | wait_for_*·TRANSIENT_LOCAL |
| 2 | 종료된 노드가 즉시 사라질 것으로 기대 | 유령 노드 | lease duration 이해(0.7.3) |
| 3 | 노드 수를 늘리며 비용 미고려 | 전체 성능 저하 | $O(N^2)$ 인지 |
| 4 | 참가자 매핑 미확인 | 예상과 다른 비용 | 캡처·ss로 확인 |
| 5 | 엔티티 동적 생성 | SEDP 폭주 | 초기화 때 생성 |
| 6 | CLI 반복 실행 | 참가자 반복 생성 | 데몬 활용 |
| 7 | 범위 제한을 보안으로 오해 | 잘못된 안전 논증 | SROS2·네트워크(13.1) |
| 8 | 멀티캐스트 불가 환경에서 기본 설정 | 디스커버리 실패 | ROS_STATIC_PEERS(3.9) |
| 9 | lease duration을 근거 없이 늘림 | 장애 감지 지연 | 트레이드오프 판단 |
| 10 | 정적 디스커버리를 개발 중 도입 | 유지보수 부담 급증 | Discovery Server 검토 |
| 11 | 디스커버리 문제를 QoS로 진단 | 원인 오판 | 층 구분(2.1.8) |
| 12 | 크래시 루프 방치 | 지속적 디스커버리 폭풍 | 재시작 백오프(14.8) |
| 13 | 비정상 종료가 /dev/shm을 채운다 | 어느 순간부터 느려짐(기능은 정상) | df -h /dev/shm 확인·--shm-size(3.3.12.3) |
| 14 | 이 규모의 느린 디스커버리를 $N^2$ 탓으로 | 실제는 알림 주기 대기 | 3.3.3을 먼저 보라(3.3.12.1) |
3.3.11 체크포인트
- SPDP와 SEDP의 전송·신뢰성·주기·내용을 각각 말할 수 있는가. 왜 두 단계로 나눴는가.
- SPDP가 주기적으로 반복되는 이유와, 그것이 시스템에 주는 상시 부하를 설명할 수 있는가.
- SEDP에서 QoS가 교환된다는 사실이
ros2 topic info -v와 QoS 불일치 판정에 어떻게 연결되는가. - 노드 시작 직후 통신이 안 되는 이유와, 그에 대한 올바른 세 가지 대처를 말할 수 있는가.
- 종료된 노드가 즉시 사라지지 않는 이유는? 정상 종료와 SIGKILL의 차이는?
- 참가자 $N$개일 때 SPDP 관계 수와 SEDP 매칭 검사 규모를 식으로 쓸 수 있는가. 노드 100개·엔드포인트 10개면 대략 어느 정도인가.
- 노드↔참가자 매핑이 비용을 좌우하는 이유와, 자신의 RMW에서 그것을 확인하는 방법을 말할 수 있는가.
- 컴포지션이 디스커버리 비용에 미치는 영향은? 대규모 시스템에서 컴포지션이 왜 "최적화"가 아니라 "요건"인가.
- 디스커버리 폭풍의 발생 조건 다섯 가지와 진단 명령을 말할 수 있는가.
ROS_AUTOMATIC_DISCOVERY_RANGE의 네 값과 각각의 용도는? 이것이 보안 수단이 아닌 이유는?ROS_STATIC_PEERS가 가치를 발휘하는 환경은?- 정적 디스커버리의 이득과 대가를 말하고, 대부분의 경우 더 나은 절충이 무엇인지 답할 수 있는가.
- 완화 수단 여덟 가지를 비용이 낮은 순으로 정렬할 수 있는가.
- 참가자 하나가
/dev/shm을 얼마나 쓰는가. Docker 기본값은 몇 참가자분인가(3.3.12.2). - 세그먼트가 남는 종료 방식과 정리되는 종료 방식은 각각 무엇인가(3.3.12.3).
/dev/shm이 가득 차면 어떤 증상으로 나타나는가(3.3.12.2).
다음 장 3.4는 그 완화 수단 중 가장 널리 쓰이는 것 — Discovery Server를 구조·설정·운영 관점에서 다룬다. $O(N^2)$을 $O(N)$으로 바꾸는 대신 무엇을 지불하는가.