본문으로 건너뛰기

ROS 2 — QoS, 메시지를 '얼마나 확실히' 주고받을지 정하기

토픽 이름·타입을 다 맞췄는데도 메시지가 안 오는 황당한 상황의 범인, QoS. 신뢰성·내구성·깊이 세 정책과 '호환성 규칙'을 비유로 풀고, 명령으로 직접 재현해본다.

ROS를 조금 다루다 보면 진짜 황당한 순간을 만난다.

  • 토픽 이름도 똑같고, 메시지 타입도 똑같이 맞췄는데 — 메시지가 안 온다. 분명 보내는 쪽은 잘 보내고 있는데, 받는 쪽이 조용하다.
  • 또는 반대로 — 카메라 영상은 초당 30장씩 쏟아지니 한두 장 놓쳐도 그만인데, “비상 정지!” 같은 명령은 단 한 번도 놓치면 안 된다. 그런데 둘 다 똑같은 토픽 방식으로 보내면, 둘을 다르게 대접할 방법이 없어 보인다.

이 두 문제의 답이 같다. 바로 QoS 다. 이 글에서 QoS가 뭔지, 왜 필요한지 비유로 풀고, 마지막엔 위 첫 번째 황당한 상황을 직접 재현해본다.

1. QoS가 뭐냐 — “어떻게 배송할지” 정하는 옵션

QoSQuality of Service, 우리말로 서비스 품질이다. 한 줄로 정의하면 이렇다.

QoS = 토픽으로 메시지를 주고받을 때 “어떤 방식으로 배송할지” 정하는 옵션 묶음.

우체국에 비유하면 딱 맞는다. 편지를 부칠 때 우리는 그냥 보낸다가 아니라 옵션을 고른다 — 등기로 보낼까 일반으로 보낼까? 빠른등기? 안 받으면 반송? QoS도 똑같이, 메시지를 보낼 때 “확실하게 vs 빠르게”, “늦게 온 사람에게도 줄까 말까”, “밀리면 몇 개나 보관할까” 같은 옵션을 고르는 것이다.

왜 이런 옵션이 필요할까? 데이터마다 필요한 게 다르기 때문이다.

  • 카메라 영상 — 양이 많고 계속 쏟아진다. 한 장 놓쳐도 다음 장이 온다. → 빠른 게 우선
  • “비상 정지” 명령 — 딱 한 번 가지만 절대 놓치면 안 된다. → 확실한 게 우선

이 둘을 똑같이 대접하면 안 된다. 그래서 토픽마다 QoS를 다르게 정한다. 이제 가장 중요한 옵션 세 개를 하나씩 본다.

2. 옵션 ① 신뢰성 (Reliability) — 등기우편 vs 전단지

첫 번째 옵션은 “메시지가 도중에 사라지면 다시 보낼까?” 이다. 두 가지 중 고른다.

  • reliable(신뢰성 보장)등기우편. 받는 쪽이 확실히 받을 때까지 다시 보낸다. 하나도 안 잃는다. 대신 확인 절차 때문에 조금 느리고 일을 더 한다
  • best_effort(최선을 다함)길거리 전단지. 한 번 뿌리고 끝. 일부가 안 닿아도 다시 안 보낸다. 대신 빠르고 가볍다
reliable — 등기 (다시 보냄) 보냄: 1 2 3 4 5 받음: 1 2 _ 4 5 3이 빠졌네 → 재전송! 최종: 1 2 3 4 5 ✓ 다 받음 best_effort — 전단지 보냄: 1 2 3 4 5 받음: 1 2 _ 4 5 3이 빠졌지만 그냥 둠 최종: 1 2 4 5 (빠름, 일부 유실)

✋ 잠깐 — 어디서 들어본 것 같다면 컴퓨터 네트워크를 배웠다면 reliable = TCP(확실하게), best_effort = UDP(빠르게)와 똑같은 발상이라고 보면 된다. 몰라도 등기 vs 전단지만 기억하면 충분하다.

언제 뭘 쓰나?

  • 명령·중요한 한 번짜리 신호reliable (놓치면 큰일)
  • 센서값처럼 계속 쏟아지는 데이터best_effort (최신값이 곧 또 온다)

3. 옵션 ② 내구성 (Durability) — 게시판 vs 칠판

두 번째 옵션은 “내가 보낸 뒤에 들어온 구독자에게도, 마지막 메시지를 줄까?” 이다.

상황을 그려보자. 발행자가 “현재 지도” 를 한 번 보냈다. 그런데 그 후에 새 구독자가 켜졌다. 이 구독자는 그 지도를 받을 수 있을까? 옵션에 따라 다르다.

  • volatile(휘발성)칠판. 썼다가 지나가면 끝. 그 순간에 안 듣고 있었으면 못 받는다. 늦게 온 사람은 다음 메시지가 올 때까지 빈손
  • transient_local(늦게 와도 보관)게시판 공지. 마지막 메시지를 붙여 둔다. 늦게 켜진 구독자도 접속하자마자 마지막 값을 즉시 받는다

✋ 잠깐 — 이걸 왜 쓰나? 지도, 로봇 설정값, 현재 상태 처럼 “자주 안 바뀌지만, 켜지면 일단 최신값을 알아야 하는” 데이터에 transient_local 을 쓴다. 안 그러면 늦게 켜진 노드는 다음 갱신 때까지 아무것도 모른 채 기다려야 한다. (예전 ROS 1의 “latched topic”이 이것이다.)

4. 옵션 ③ 깊이 (Depth) — 우편함 크기

세 번째 옵션은 “받는 쪽이 잠깐 바쁘면, 메시지를 몇 개까지 쌓아둘까?” 이다.

받는 노드가 처리 속도가 느려 잠깐 밀릴 수 있다. 그동안 도착한 메시지는 대기열(큐) 에 쌓인다. 이 큐의 크기가 깊이(depth)다. 우편함과 같다 — 우편함이 10칸이면 10통까지 보관하고, 넘치면 가장 오래된 것부터 버린다.

  • 깊이 10 이면 → 최근 10개까지 보관. 받는 쪽이 밀려도 최근 10개는 안 잃는다
  • 작게 잡으면(예 1) → 항상 가장 최신 1개만. 오래된 건 미련 없이 버림 (최신값만 중요할 때)

✋ 잠깐 — 발행자를 만들 때 끝에 붙던 그 숫자 코드로 발행자를 만들 때 create_publisher(타입, 토픽이름, 10) 처럼 끝에 숫자 10 을 적는다. 그게 바로 이 깊이다. 무심코 적던 10 이 사실은 “우편함 10칸” 이라는 QoS 설정이었던 것이다.

5. ⭐ 가장 중요 — 안 맞으면 아예 연결이 안 된다

여기가 이 글의 핵심이고, 맨 앞 “이름·타입 다 맞췄는데 메시지가 안 와요” 의 범인이다.

발행자와 구독자의 QoS가 서로 맞지 않으면, ROS는 둘을 아예 연결하지 않는다. 토픽 이름이 같아도, 타입이 같아도 — 조용히 아무 메시지도 안 간다.

규칙은 한 문장이다. “구독자가 요구하는 수준 ≤ 발행자가 제공하는 수준” 이어야 한다. 신뢰성으로 예를 들면:

발행자(보내는 쪽)구독자(받는 쪽)연결될까?
reliable (등기)reliable (등기로 받기)✅ 된다
reliable (등기)best_effort (대충 받기)✅ 된다 (덜 까다로운 요구)
best_effort (전단지)best_effort (대충 받기)✅ 된다
best_effort (전단지)reliable (등기로 받기)안 된다

마지막 줄이 함정이다. 발행자는 “전단지로 보낼게” 인데 구독자가 “등기로만 받을래” 라고 우기면, 우체국은 약속이 안 맞으니 배달을 안 한다. 내구성(durability)도 같은 식이다 — 구독자가 transient_local 을 요구하는데 발행자가 volatile 이면 연결 안 됨.

이 사실만 알아도, “이름도 타입도 맞는데 왜 안 오지?” 의 90%는 “아, QoS가 안 맞았구나” 로 풀린다.

6. 직접 해보기 — 일부러 안 맞춰서 “안 오는 것” 보기

이제 맨 앞의 황당한 상황을 손으로 재현해본다. 글자를 주고받는 가장 단순한 토픽으로 해보자.

터미널 1 — best_effort(전단지)로 발행:

$ source /opt/ros/jazzy/setup.bash
$ ros2 topic pub /demo std_msgs/msg/String "{data: '안녕'}" --qos-reliability best_effort
publishing #1: std_msgs.msg.String(data='안녕')
publishing #2: std_msgs.msg.String(data='안녕')
...

→ 1초에 한 번씩 잘 보내고 있다. 이제 일부러 등기로만 받겠다는 구독자를 켠다.

터미널 2 — reliable(등기)로 구독:

$ source /opt/ros/jazzy/setup.bash
$ ros2 topic echo /demo --qos-reliability reliable
[WARN] [rmw]: New publisher discovered on topic '/demo', offering incompatible QoS. No messages will be sent.

아무 메시지도 안 온다. 토픽 이름(/demo)도, 타입(String)도 똑같은데! “호환되지 않는 QoS” 라는 경고만 뜨고 조용하다. 맨 앞의 황당한 상황을 우리가 직접 만든 것이다.

이제 구독자를 best_effort로 바꿔서 다시 켜보자.

$ ros2 topic echo /demo --qos-reliability best_effort
data: 안녕
---
data: 안녕
---

이번엔 잘 온다! 발행자(전단지)와 구독자(대충 받기)의 약속이 맞았기 때문이다.

✋ 잠깐 — 누가 무슨 QoS를 쓰는지 확인하는 법 어떤 토픽에 붙은 발행자·구독자가 각자 무슨 QoS 를 쓰는지 그대로 보여주는 명령이 있다.

$ ros2 topic info /demo --verbose

출력에 각 발행자/구독자별로 Reliability: BEST_EFFORT, Durability: VOLATILE 같은 설정이 줄줄이 찍힌다. “왜 안 오지?” 싶을 때 양쪽 QoS를 여기서 대조하면 범인이 바로 보인다.

✏️ 직접 해보기 — 내구성도 느껴보기

  1. 터미널 1에서 --qos-durability transient_local 을 붙여 발행을 켜둔다 (그대로 둠)
  2. 한참 기다렸다가 터미널 2에서 ros2 topic echo /demo --qos-durability transient_local 을 켜본다 → 늦게 켰는데도 마지막 메시지를 즉시 받는다 (게시판 공지!). transient_local 을 빼고(=기본 volatile) 켜면, 켠 이후에 온 것만 받는다.

7. 그래서 — 데이터 종류별로 뭘 쓰나

QoS는 종류가 더 있지만, 실전에서 거의 이 조합들로 정리된다.

데이터 종류신뢰성내구성이유
센서값 (카메라·라이다 등)best_effortvolatile양 많고 최신값이 곧 또 옴 — 빠른 게 우선
명령·중요 신호 (정지 등)reliablevolatile한 번도 놓치면 안 됨
지도·정적 정보reliabletransient_local자주 안 바뀌지만 늦게 켜도 최신값 필요
현재 상태·설정reliabletransient_local켜지면 일단 마지막 상태를 알아야 함

ROS에는 이런 자주 쓰는 조합을 미리 묶어둔 “QoS 프로파일” 도 있다 (예: 센서용 프로파일 = best_effort + 작은 깊이). 처음에는 기본값(reliable·volatile·깊이 10) 으로 충분하고, 위 같은 특별한 이유가 있을 때만 바꾸면 된다.

8. 막히면 — 이 증상이면 이 원인

이런 증상이면십중팔구 원인
이름·타입 다 맞는데 메시지가 안 옴QoS 불일치ros2 topic info /토픽 --verbose 로 양쪽 대조
incompatible QoS 경고가 뜸발행/구독의 신뢰성 또는 내구성이 안 맞음 (§5 표)
늦게 켠 구독자가 마지막 값을 못 받음발행자가 volatile 임 → 양쪽 transient_local 필요
받는 쪽이 가끔 메시지를 건너뜀깊이(우편함)가 너무 작거나 처리 속도가 느림
ros2: command not found그 터미널에서 source 안 함

가장 흔한 건 신뢰성 불일치다. 막히면 양쪽 QoS부터 의심하자.

한 줄로 박아둘 것

  1. QoS = “어떻게 배송할지” 정하는 옵션 묶음. 데이터마다 필요한 보장이 달라서 존재한다 (우체국 옵션 모형)
  2. 신뢰성(Reliability)reliable(등기, 다시 보냄) vs best_effort(전단지, 빠름). 명령은 reliable, 센서는 best_effort
  3. 내구성(Durability)volatile(칠판) vs transient_local(게시판, 늦게 와도 마지막 값 받음). 지도·상태에 유용
  4. 깊이(Depth) — 밀릴 때 몇 개까지 보관하는 우편함 크기. 발행자 만들 때 끝에 적던 10 이 이것
  5. ⭐ 안 맞으면 연결 자체가 안 된다 — 이름·타입이 같아도 QoS가 안 맞으면 조용히 아무것도 안 온다. “요청 ≤ 제공” 규칙
  6. “왜 안 오지?” 싶으면 → ros2 topic info 토픽 --verbose 로 양쪽 QoS를 대조하는 게 첫 수