제3부 미들웨어: DDS·RTPS·RMW
3.4 Discovery Server와 대규모 그래프
3.4.1 이 장에서 배우는 것 / 선수 지식
3.3에서 디스커버리 비용이 $O(N^2)$임을 봤다. Discovery Server는 그것을 $O(N)$으로 바꾼다. 방법은 단순하다 — 모두가 모두에게 알리는 대신, 중앙의 서버 하나가 정보를 모아 배포한다.
여기서 즉시 드는 의문이 있다. "그럼 ROS 1의 마스터로 돌아가는 것 아닌가?" 절반은 맞고 절반은 다르다. 이 장은 그 차이와, 중앙화가 되돌려주는 대가를 정직하게 다룬다.
배우는 것:
- 구조 — Client·Server·Super-client의 역할.
- 비용 구조의 변화 — $O(N^2)$이 왜 $O(N)$이 되는가.
- 설정 —
ROS_DISCOVERY_SERVER와 서버 실행. - 가용성 설계 — 서버가 종료되면 어떻게 되는가, 이중화·백업.
- ROS 1 마스터와의 차이 — 무엇이 여전히 분산인가.
- 판단 기준 — 언제 쓰고 언제 쓰지 않는가.
- 대안과의 비교 — 도메인 분리·정적 디스커버리·zenoh.
선수 지식: 3.3(디스커버리 비용), 3.2(포트·GUID), 3.1(참가자), 0.4(환경변수).
정직: Discovery Server는 Fast DDS(eProsima)의 기능이며 DDS 표준이 아니다. 따라서
rmw_fastrtps_cpp를 쓸 때만 사용할 수 있다(Jazzy 기본 RMW이므로 대부분 해당 — BK-05). 다른 벤더는 각자의 방식을 갖거나 지원하지 않는다. 아래 환경변수·인자 형식은 eProsima 공개 문서 기준이며 이 환경에서 실행 검증하지 않았다(확인 필요: 인자 형식)(목차 3). 다만 CLI 이름은 실행 확인했다(88차 — Jazzy ·ros:jazzy-ros-base):/opt/ros/jazzy/bin/에fastdds·fast-discovery-server·ros-discovery셋이 함께 설치된다.fastdds는discovery·shm같은 서브커맨드를 갖는 우산 명령이고, 나머지 둘은 같은 도구(eProsima Server-Client discovery auxiliary generator tool 1.0.1)를 직접 부르는 이름이다 —ros-discovery는--help가 자신을fastdds discovery로 소개하므로 사실상 별칭이다.검증 상태(추가): 이 장의 서술 가운데 1곳은 이후 회차에서 Jazzy 실물과 대조해 확인했다 — 본문의 (실행 확인) 표기가 그것이다. 표기가 없는 서술은 위 고지가 그대로 적용된다(목차 3의 검증 범위 표).
3.4.2 구조 — 중앙 집중 디스커버리
| 역할 | 설명 |
|---|---|
| Server | 디스커버리 정보를 모으고 배포. 데이터는 지나가지 않는다 |
| Client | 서버에만 디스커버리 정보를 보내고 받는다 |
| Super-client | 클라이언트이면서 그래프 전체 정보를 받는다 — 내성(introspection) 도구용 |
결정적으로 중요한 사실: 서버는 디스커버리만 중계한다. 실제 데이터는 여전히 피어 투 피어다.
이것이 ROS 1 마스터와의 핵심 차이다. 마스터도 데이터를 중계하지는 않았지만, ROS 1은 마스터가 없으면 새 연결이 전혀 불가능했고 XML-RPC 왕복이 통신 경로에 있었다. Discovery Server는 이미 맺어진 연결이 서버와 무관하게 계속 동작한다.
Super-client가 필요한 이유: 일반 Client는 자기가 필요한 상대만 알면 된다. 그런데 ros2 topic list·rqt_graph 같은 도구는 그래프 전체를 알아야 한다. Super-client 모드가 그것을 제공한다.
함정: Discovery Server를 쓰면서 CLI를 일반 Client로 실행하면 그래프가 부분적으로만 보인다. "노드가 있는데
ros2 node list에 안 나온다"의 원인이 이것일 수 있다. 도구는 Super-client로 설정해야 한다(3.4.4).
3.4.3 비용 구조의 변화
단순 디스커버리: 참가자 $N$개가 서로 알아야 하므로
$$\text{관계 수} = \binom{N}{2} = \frac{N(N-1)}{2} = O(N^2)$$
Discovery Server: 각 참가자가 서버 하나와만 관계를 맺으므로
$$\text{관계 수} = N = O(N)$$
| 참가자 수 | 단순 디스커버리 관계 | Discovery Server 관계 | 비율 |
|---|---|---|---|
| 10 | 45 | 10 | 4.5× |
| 50 | 1,225 | 50 | 24.5× |
| 100 | 4,950 | 100 | 49.5× |
| 200 | 19,900 | 200 | 99.5× |
| 500 | 124,750 | 500 | 249.5× |
추가 이득:
| 이득 | 설명 |
|---|---|
| 멀티캐스트 불필요 | 서버 주소를 알면 유니캐스트로 충분 — 무선·클라우드·컨테이너에서 결정적(3.9) |
| 시작 시간 단축 | 서버가 이미 정보를 갖고 있다 |
| 트래픽 예측 가능 | 서버 중심의 단순한 패턴 |
| 서브넷 경계 통과 | 라우팅만 되면 동작 |
"멀티캐스트 불필요"가 실무에서 가장 자주 채택 이유가 된다. 디스커버리 규모가 문제가 아니라 멀티캐스트가 막힌 환경이라서 Discovery Server를 쓰는 경우가 많다 — Docker bridge 네트워크, 일부 클라우드 VPC, Wi-Fi AP가 멀티캐스트를 억제하는 환경(3.9).
3.4.4 설정
서버 실행:
# 서버 ID 0, 포트 11811에서 대기
fastdds discovery --server-id 0 --port 11811
# Jazzy에는 fastdds · fast-discovery-server · ros-discovery 셋 다 설치된다 (88차 실행 확인)
클라이언트 설정:
export ROS_DISCOVERY_SERVER="192.168.1.100:11811"
ros2 run demo_nodes_cpp talker
여러 서버 지정(이중화):
# 세미콜론으로 구분 — 위치가 서버 ID에 대응한다
export ROS_DISCOVERY_SERVER="192.168.1.100:11811;192.168.1.101:11811"
빈 자리를 두어 특정 ID만 지정할 수도 있다:
export ROS_DISCOVERY_SERVER=";192.168.1.101:11811" # 서버 ID 1만 사용
CLI를 Super-client로:
# XML 프로파일에서 super-client 모드를 지정하고
export FASTRTPS_DEFAULT_PROFILES_FILE=super_client.xml
export ROS_DISCOVERY_SERVER="192.168.1.100:11811"
ros2 topic list
Jazzy(Fast DDS 2.14.x)에서 프로파일 파일 변수는
FASTRTPS_DEFAULT_PROFILES_FILE이 맞다(실행 확인) —FASTDDS_접두사 이름은 이 버전의 라이브러리에 존재하지 않는다(3.7.3).
정직: Super-client 설정은 XML이 필요하며 환경변수만으로는 부족하다
(확인 필요: 최신 버전에서 간소화됐을 수 있음). eProsima 문서의 현재 절차를 확인하기 바란다. 이 부분이 Discovery Server 도입 시 가장 자주 막히는 지점이다.
launch에서:
import os
from launch import LaunchDescription
from launch.actions import SetEnvironmentVariable
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
SetEnvironmentVariable('ROS_DISCOVERY_SERVER', '192.168.1.100:11811'),
Node(package='my_pkg', executable='my_node'),
])
systemd로 서버 운영(14.8):
[Unit]
Description=Fast DDS Discovery Server
After=network-online.target
[Service]
ExecStart=/opt/ros/jazzy/bin/fastdds discovery --server-id 0 --port 11811
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
Restart=always가 중요하다 — 서버는 상시 가용해야 한다(3.4.5).
3.4.5 서버가 종료되면 어떻게 되는가
가장 중요한 질문이다. 중앙 요소를 도입했으니 그 실패를 설계해야 한다.
| 상황 | 서버 사망 시 |
|---|---|
| 이미 통신 중인 노드들 | 정상 동작 유지 |
| 새 노드 시작 | 아무도 못 찾음 |
| 노드 재시작 | 재연결 실패 |
| 새 토픽·서비스 생성 | 매칭 안 됨 |
| CLI 조회 | 실패 또는 부분 정보 |
즉 "실행되고 있는 시스템은 유지되지만, 변화는 멈춘다." 크래시 후 자동 재시작에 의존하는 시스템(14.8)에서는 이것이 치명적일 수 있다 — 노드가 재시작해도 다시 연결되지 못한다.
가용성 대책:
① 서버 이중화(다중 서버)
export ROS_DISCOVERY_SERVER="10.0.0.1:11811;10.0.0.2:11811"
클라이언트가 여러 서버에 연결하므로 하나가 종료돼도 계속된다. 서버끼리 정보를 공유하도록 구성할 수도 있다(확인 필요: 서버 간 연결 설정 방법).
② 자동 재시작 — systemd Restart=always. 다만 재시작한 서버는 정보를 잃은 상태이므로, 클라이언트들이 다시 알려줄 때까지 그래프가 불완전하다.
③ 백업 서버 모드 — Fast DDS는 디스커버리 정보를 디스크에 저장했다 복구하는 백업 모드를 제공한다(확인 필요: Jazzy 버전에서의 사용법). 재시작 후 상태를 되찾을 수 있다.
④ 서버 배치 설계 — 서버를 로봇 안(온보드)에 둘지 외부에 둘지가 가용성을 좌우한다.
| 배치 | 장점 | 단점 |
|---|---|---|
| 로봇 온보드 | 네트워크 단절에도 로봇 내부는 정상 | 로봇마다 서버 |
| 중앙 서버 | 플릿 전체가 한 그래프 | 네트워크 단절 시 전부 마비 |
| 계층(로봇 내 + 중앙) | 균형 | 설정 복잡 |
견해: 로봇에 온보드 서버를 두고, 필요하면 중앙 서버와 연결하는 계층 구조가 안전하다. 무선이 끊겨도 로봇 내부 그래프는 유지돼야 한다 — 이것은 성능이 아니라 안전 요건이다(14.10). 중앙 서버 하나에 모든 로봇을 물리는 구성은 무선 환경에서 위험하다.
3.4.6 ROS 1 마스터와의 차이
"마스터로 돌아간 것 아닌가"에 대한 정확한 답이다.
| 축 | ROS 1 마스터 | Discovery Server | 단순 디스커버리 |
|---|---|---|---|
| 데이터 경로 | 피어 투 피어 | 피어 투 피어 | 피어 투 피어 |
| 디스커버리 | 중앙 | 중앙 | 분산 |
| 마스터 사망 시 기존 연결 | 유지 | 유지 | — |
| 마스터 사망 시 새 연결 | 불가 | 불가 | — |
| 이중화 | 불가(단일 마스터) | 가능(다중 서버) | — |
| 이름 등록 | 마스터가 소유 | 서버는 중계만 | 각자 |
| 필수 여부 | 필수 | 선택 | 기본 |
두 가지 실질적 차이:
- 선택 사항이다. 환경변수 하나로 켜고 끈다. 코드는 그대로다. ROS 1은 마스터 없이 동작할 방법이 없었다.
- 이중화가 가능하다. 단일 장애점을 설계로 제거할 수 있다.
견해: "중앙 요소는 불리하다"는 교조적 태도는 도움이 안 된다. ROS 2가 마스터를 없앤 것은 옳았지만, 그 결과 $O(N^2)$ 디스커버리라는 대가를 얻었다. Discovery Server는 필요한 곳에서 선택적으로 중앙화를 되돌리는 도구이며, 이중화로 원래 문제(단일 장애점)를 완화한다. 중요한 것은 선택권이 사용자에게 있다는 것이다.
3.4.7 언제 쓰고 언제 쓰지 않는가
쓸 만한 경우:
| 상황 | 이유 |
|---|---|
| 멀티캐스트가 막힌 환경 | Docker bridge·클라우드·일부 Wi-Fi(3.9) |
| 참가자 100개 이상 | $O(N^2)$ 부담 |
| 서브넷을 넘는 통신 | 멀티캐스트가 라우팅 안 됨 |
| 디스커버리 트래픽이 측정으로 확인됨 | 근거 있는 도입 |
| 시작 시간이 문제 | 단축 효과 |
쓰지 않는 편이 나은 경우:
| 상황 | 이유 |
|---|---|
| 노드 수십 개 이하 + 정상 LAN | 불필요한 복잡도 |
| 단일 로봇 개발 환경 | 관리 부담이 이득 초과 |
| 서버 운영을 감당할 수 없는 조직 | 새 장애 지점 추가 |
| 다른 RMW 벤더 사용 | Fast DDS 전용(3.4.1) |
| WAN·고지연 링크가 주된 문제 | zenoh가 더 적합(3.10) |
견해: "규모가 커서" 도입하는 경우보다 "멀티캐스트가 안 돼서" 도입하는 경우가 실무에서 더 많다. 그리고 후자라면
ROS_STATIC_PEERS(3.3.7)로 먼저 시도해 볼 가치가 있다 — 노드가 적고 주소가 고정이면 서버 없이 해결된다. 가장 단순한 해법부터 시도하라.
3.4.8 도입 절차와 검증
6번 검증이 중요하다. Discovery Server 도입 후 흔한 실패가 "일부 노드만 보인다" 이다.
# 도입 전후 비교
ros2 node list | sort > before.txt
# … Discovery Server 전환 …
ros2 node list | sort > after.txt
diff before.txt after.txt # 차이가 없어야 한다
7번 장애 시험도 반드시 해야 한다.
# 서버를 강제 종료하고 기존 통신이 유지되는지
sudo systemctl stop fastdds-discovery
ros2 topic hz /scan # 계속 흘러야 정상
# 새 노드가 연결되지 못하는 것을 확인 (예상된 동작)
ros2 run demo_nodes_cpp listener # 안 붙어야 정상
# 서버 복구 후 회복 확인
sudo systemctl start fastdds-discovery
이 시험을 안 하면 현장에서 처음 겪게 된다. 서버 장애 시 시스템이 어떻게 행동하는지 미리 아는 것과 모르는 것의 차이는 크다.
8번 측정:
| 지표 | 방법 |
|---|---|
| 디스커버리 트래픽 | 패킷 캡처 비율(3.2.8) |
| 시작 시간 | 부팅부터 전체 노드 활성까지 |
| CPU | 정상 상태에서의 사용률 |
| 메모리 | 참가자당 캐시 감소 |
3.4.9 대안과의 비교
| 수단 | 디스커버리 비용 | 멀티캐스트 필요 | 유연성 | 단일 장애점 | 벤더 종속 |
|---|---|---|---|---|---|
| 단순 디스커버리 | $O(N^2)$ | ✅ 필요 | 높음 | 없음 | 없음 |
ROS_STATIC_PEERS | $O(N^2)$(피어 한정) | ❌ 불필요 | 중간 | 없음 | 낮음 |
| Discovery Server | $O(N)$ | ❌ 불필요 | 높음 | 있음(이중화 가능) | Fast DDS |
| 정적 디스커버리 | ~0 | ❌ 불필요 | 없음 | 없음 | 높음 |
| 도메인 분리 | 도메인별 $O(n^2)$ | ✅ 필요 | 중간 | 없음 | 없음 |
| zenoh | 구조적으로 다름 | ❌ 불필요 | 높음 | 라우터 모드면 있음 | rmw_zenoh |
zenoh와의 관계가 특히 중요하다. zenoh는 Discovery Server처럼 라우터를 둘 수도 있고 완전 peer-to-peer로 쓸 수도 있으며, WAN·무선을 처음부터 염두에 두고 설계됐다. Discovery Server가 "DDS의 디스커버리 문제를 DDS 안에서 푸는" 접근이라면, zenoh는 "미들웨어를 바꾸는" 접근이다(3.10).
견해: 선택 순서는 대략 이렇다. ① 컴포지션·엔티티 정적화로 해결되는지 본다(3.3.9) → ② 멀티캐스트 문제면
ROS_STATIC_PEERS→ ③ 규모·멀티캐스트 문제가 함께면 Discovery Server → ④ 무선·WAN·플릿이 본질이면 zenoh 검토. 각 단계가 다음 단계보다 비용이 낮다.
3.4.10 함정 카탈로그
| # | 함정 | 증상 | 대처 |
|---|---|---|---|
| 1 | CLI를 Super-client로 설정 안 함 | 그래프가 부분만 보임 | XML 프로파일 설정 |
| 2 | 서버 장애 시나리오 미시험 | 현장에서 처음 겪음 | 도입 전 시험(3.4.8) |
| 3 | 중앙 서버 하나에 전 로봇 | 무선 단절 시 전체 마비 | 온보드 서버·계층 구조 |
| 4 | Restart 설정 누락 | 서버 사망 후 복구 안 됨 | systemd Restart=always |
| 5 | 일부 노드만 환경변수 적용 | 그래프 분열 | 배포 일관성 확인 |
| 6 | 다른 RMW로 전환 | Discovery Server 무효 | Fast DDS 전용임을 인지 |
| 7 | 규모 문제가 아닌데 도입 | 불필요한 복잡도 | 더 값싼 수단 먼저 |
| 8 | 서버 재시작 후 정보 손실 미고려 | 일시적 그래프 불완전 | 백업 모드·이중화 |
| 9 | 도입 후 검증 생략 | 노드 누락을 못 알아챔 | diff로 대조 |
| 10 | 서버 포트 방화벽 미개방 | 아무도 연결되지 못함 | 11811 등 허용 |
3.4.11 체크포인트
- Discovery Server의 세 가지 역할(Server·Client·Super-client)을 설명할 수 있는가. Super-client가 필요한 이유는?
- "서버는 디스커버리만 중계하고 데이터는 피어 투 피어"라는 사실이 왜 결정적인가.
- 참가자 100개일 때 단순 디스커버리와 Discovery Server의 관계 수를 각각 계산할 수 있는가.
- 실무에서 Discovery Server 채택 이유로 규모보다 자주 등장하는 것은 무엇인가.
- 서버가 종료됐을 때 유지되는 것과 불가능해지는 것을 각각 말할 수 있는가. 자동 재시작에 의존하는 시스템에서 왜 위험한가.
- 가용성 대책 네 가지를 들 수 있는가. 서버 배치를 온보드로 두는 것이 왜 안전 요건인가.
- ROS 1 마스터와 Discovery Server의 실질적 차이 두 가지는 무엇인가.
- Discovery Server를 쓰지 않는 편이 나은 상황 다섯 가지를 들 수 있는가.
- 도입 절차 여덟 단계 중 가장 자주 생략되고 가장 위험한 것은?
- 완화 수단 선택 순서를 값싼 것부터 말할 수 있는가. zenoh는 어느 단계에서 검토하는가.
다음 장 3.5는 지금까지 "벤더마다 다르다"고 반복해 온 그 경계 — RMW 계층을 정면으로 다룬다. rmw API가 무엇을 약속하고, 구현 교체가 실제로 어떻게 일어나며, 어디까지가 이식 가능한가.