intermediate - Quality of Service settings

ROS 2는 Quality of Service(QoS) 정책으로 노드 간 통신을 조정한다. 설정에 따라 TCP처럼 신뢰(reliable) 하게 할 수도, UDP처럼 최선 노력(best effort) 으로 할 수도 있고, 그 사이 다양한 조합이 가능하다. ROS 1이 주로 TCP에 의존한 것과 달리, ROS 2는 DDS 전송의 유연성을 활용해 손실이 있는 무선 환경에서는 best effort가, 실시간 시스템에서는 적절한 QoS 프로파일이 쓰인다.

여러 QoS 정책(policies) 이 모여 QoS 프로파일(profile) 을 이룬다. 시나리오에 맞는 정책 선택이 복잡하므로 ROS 2는 센서 데이터 등 자주 쓰는 용도별 기본 프로파일을 제공하고, 필요 시 개별 정책을 덮어쓸 수 있다. QoS 프로파일은 퍼블리셔·서브스크립션·서비스 서버·클라이언트 각각에 지정할 수 있다. 서로 다른 프로파일을 쓰면 호환되지 않아 메시지가 전달되지 않을 수 있다.

공식 개념 문서: Quality of Service settings (Jazzy)

QoS 정책(Policies)

기본 QoS 프로파일에 포함되는 정책은 다음과 같다.

정책옵션설명
HistoryKeep last / Keep allKeep last: 최대 N개 샘플만 유지(depth로 N 설정). Keep all: 모두 유지(미들웨어 리소스 한도 내).
DepthQueue sizeHistory가 "keep last"일 때만 적용.
ReliabilityBest effort / ReliableBest effort: 전달 시도하나 손실 가능. Reliable: 전달 보장, 재시도 가능.
DurabilityTransient local / VolatileTransient local: 늦게 붙는 서브스크립션을 위해 퍼블리셔가 샘플 유지. Volatile: 유지 안 함.
DeadlineDuration토픽에 연속 메시지가 나갈 때 기대하는 최대 간격.
LifespanDuration발행 후 수신까지 이 시간을 넘기면 만료로 간주하고 조용히 버림.
LivelinessAutomatic / Manual by topicAutomatic: 노드의 퍼블리셔 중 하나만 메시지를 보내도 노드가 살아 있다고 간주. Manual by topic: 퍼블리셔 API로 수동으로 liveliness 선언.
Lease DurationDuration퍼블리셔가 이 시간 안에 liveliness를 알리지 않으면 liveliness 상실로 간주(장애 징후일 수 있음).

Duration이 아닌 정책에는 system default(미들웨어 기본값) 옵션이 있고, Duration 정책에는 default(미지정, 보통 무한대로 해석)가 있다.

QoS 프로파일(Profiles)

프로파일은 특정 용도에 맞게 잘 맞춰진 정책 묶음이다.

  • 퍼블리셔·서브스크립션 default: ROS 1과 비슷한 동작을 위해 history keep last(depth 10), reliable, volatile, liveliness system default. Deadline·lifespan·lease duration은 default.
  • 서비스: 신뢰성이 중요. Durability는 volatile이어야 하며, 그렇지 않으면 재시작한 서버가 오래된 요청을 받을 수 있다(클라이언트는 중복 응답으로 보호되지만 서버는 아님).
  • 센서 데이터: 적시에 최신 샘플을 받는 것이 더 중요하므로 best effort작은 queue를 쓰는 프로파일.
  • 파라미터: 서비스 기반이므로 비슷한 프로파일이지만, 요청이 유실되지 않도록 큰 queue depth를 쓴다.
  • System default: 모든 정책을 RMW 구현체 기본값으로 둔다. RMW마다 기본값이 다를 수 있다.

상세 정책 값은 원문 프로파일 링크를 참고한다.

QoS 호환(Compatibilities)

아래 내용은 퍼블리셔·서브스크립션 기준이며, 서비스 서버·클라이언트에도 동일하게 적용된다.

퍼블리셔와 서브스크립션은 각각 QoS 프로파일을 가진다. Request vs Offered 모델로 호환 여부가 정해진다. 서브스크립션은 받아들일 최소 품질(requested) 를, 퍼블리셔는 제공할 최대 품질(offered) 를 낸다. 요청이 제안보다 더 엄격하지 않을 때만 연결이 성립한다. 한 퍼블리셔에 여러 서브스크립션이 붙을 수 있고 요청 프로파일이 서로 달라도 되며, 다른 퍼블리셔·서브스크립션 존재 여부는 이 쌍의 호환에 영향을 주지 않는다.

Reliability 호환

퍼블리셔서브스크립션호환
Best effortBest effort
Best effortReliable아니오
ReliableBest effort
ReliableReliable

Durability 호환

퍼블리셔서브스크립션호환결과
VolatileVolatile새 메시지만
VolatileTransient local아니오통신 없음
Transient localVolatile새 메시지만
Transient localTransient local새·이전 메시지 모두

늦게 붙는 서브스크립터도 마지막 메시지를 보려면(latched 토픽처럼) 퍼블리셔와 서브스크립션 모두 Transient local이어야 한다.

Deadline / Lease duration 호환
x, _y_를 임의의 유효한 duration이라 하면: 퍼블리셔 default + 서브스크립션 x 요청 → 호환 안 됨. 퍼블리셔 x + 서브스크립션 default → 호환. 퍼블리셔 x + 서브스크립션 y 에서 y > x 이면 호환, y < x 이면 비호환.

Liveliness 호환

퍼블리셔서브스크립션호환
AutomaticAutomatic
AutomaticManual by topic아니오
Manual by topicAutomatic
Manual by topicManual by topic

연결이 되려면 호환에 관여하는 모든 정책이 호환되어야 한다. Reliability는 맞아도 Durability가 맞지 않으면 연결되지 않는다. 연결이 안 되면 메시지는 전달되지 않으며, 이 상황을 감지하는 메커니즘이 있다(아래 QoS 이벤트).

ROS 1과 비교

ROS 1에서는 같은 토픽·같은 메시지 타입이면 퍼블리셔와 서브스크라이버가 연결되었다. ROS 2에서는 요청·제공 QoS가 호환되지 않으면 연결되지 않는다는 점을 유의해야 한다.

QoS 이벤트(Events)

일부 QoS 정책과 연관된 이벤트가 있으며, 퍼블리셔·서브스크립션에 콜백을 등록해 처리할 수 있다(토픽 메시지 콜백과 유사).

퍼블리셔 쪽 이벤트

  • Offered deadline missed: deadline QoS에 정한 간격 안에 메시지를 발행하지 못함.
  • Liveliness lost: lease duration 안에 liveliness를 알리지 못함.
  • Offered incompatible QoS: 같은 토픽의 서브스크립션이 요청한 QoS를 제가 제공하는 QoS로 만족할 수 없어 연결되지 않음.

서브스크립션 쪽 이벤트

  • Requested deadline missed: deadline QoS에 정한 간격 안에 메시지를 받지 못함.
  • Liveliness changed: 구독 중인 토픽의 퍼블리셔 중 하나 이상이 lease duration 안에 liveliness를 알리지 못함.
  • Requested incompatible QoS: 같은 토픽의 퍼블리셔가 제공하는 QoS가 내가 요청한 QoS를 만족하지 않아 연결되지 않음.

Matched 이벤트

QoS 이벤트 외에, 퍼블리셔와 서브스크립션이 연결되거나 끊어질 때 Matched 이벤트가 발생한다. 퍼블리셔·서브스크립션 각각에 콜백을 달 수 있다.

  • 퍼블리셔: 토픽이 같고 QoS가 호환되는 서브스크립션을 찾았을 때, 또는 이미 연결된 서브스크립션이 끊어졌을 때.
  • 서브스크립션: 토픽이 같고 QoS가 호환되는 퍼블리셔를 찾았을 때, 또는 이미 연결된 퍼블리셔가 끊어졌을 때.

이벤트 사용 예시는 rclcpp demo, rclpy demo를 참고하면 된다.

Python(rclpy) QoS 설정 예시

기본·센서 데이터 프로파일 사용

import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile, qos_profile_default, qos_profile_sensor_data
from std_msgs.msg import String

class ExampleNode(Node):
    def __init__(self):
        super().__init__('example_qos_node')
        # 기본 프로파일: reliable, volatile, depth 10
        self.pub_default = self.create_publisher(String, 'topic_default', qos_profile_default)
        # 센서 데이터용: best effort, 작은 queue (적시성 우선)
        self.sub_sensor = self.create_subscription(
            String, 'topic_sensor', self.sensor_callback, qos_profile_sensor_data
        )

    def sensor_callback(self, msg):
        self.get_logger().info(f'Received: {msg.data}')

커스텀 QoS 프로파일 (직접 정책 지정)

from rclpy.qos import (
    QoSProfile,
    QoSReliabilityPolicy,
    QoSDurabilityPolicy,
    QoSHistoryPolicy,
)

# 예: reliable + transient local (늦게 붙는 구독자도 마지막 메시지 수신, latched 유사)
qos_latched = QoSProfile(
    reliability=QoSReliabilityPolicy.RELIABLE,
    durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
    history=QoSHistoryPolicy.KEEP_LAST,
    depth=1,
)
self.pub = self.create_publisher(String, 'topic_latched', qos_latched)
self.sub = self.create_subscription(String, 'topic_latched', self.cb, qos_latched)

# 예: best effort + volatile, depth 5 (손실 허용·작은 버퍼)
qos_best_effort = QoSProfile(
    reliability=QoSReliabilityPolicy.BEST_EFFORT,
    durability=QoSDurabilityPolicy.VOLATILE,
    history=QoSHistoryPolicy.KEEP_LAST,
    depth=5,
)
self.sub_best_effort = self.create_subscription(String, 'topic_fast', self.cb, qos_best_effort)

프로파일 복사 후 일부만 변경

from rclpy.qos import QoSProfile, QoSHistoryPolicy, QoSReliabilityPolicy, QoSDurabilityPolicy

# 센서 데이터와 비슷하되 depth만 3으로
qos = QoSProfile(
    history=QoSHistoryPolicy.KEEP_LAST,
    depth=3,
    reliability=QoSReliabilityPolicy.BEST_EFFORT,
    durability=QoSDurabilityPolicy.VOLATILE,
)
self.sub = self.create_subscription(String, 'topic', self.cb, qos)

퍼블리셔와 서브스크립션의 QoS가 호환되어야 연결되므로, 같은 토픽을 쓰는 쌍은 위 호환 표를 참고해 맞춰 주면 된다.