# 사용자 정의 QoS 프로필 설정 예시

#### QoS 프로필 개념 정리

ROS2에서 Quality of Service(QoS) 설정은 노드 간 통신의 신뢰도, 지연 허용 범위, 버퍼링 전략 등을 세부적으로 제어하기 위해 사용된다. QoS 프로필은 메시지 교환이 어떻게 이루어져야 하는지 정의하며, 주요 파라미터는 다음과 같다.

* **Reliability**
  * Reliable: 모든 메시지가 목적지에 전달되도록 보장한다.
  * Best Effort: 네트워크 상태가 허락하는 한 빠르게 전달하지만, 메시지 손실이 발생할 수 있다.
* **Durability**
  * Volatile: 메시지를 발행하는 동안에만 구독 노드로 전달된다.
  * Transient Local: 발행자가 메시지를 보존하고 있으므로, 구독자가 늦게 연결되더라도 최근 메시지를 받아볼 수 있다.
* **Deadline**: 발행-구독 주기 내에서 메시지가 정해진 시간 안에 도착해야 하는지에 대한 요구 사항이다. 예를 들어, $T\_d$라는 데드라인 시간 안에 메시지가 반드시 도착해야 한다면, 시스템은 해당 시간을 초과하지 않도록 보장하려고 한다.
* **Lifespan**: 메시지가 특정 시간($T\_l$)이 지날 때까지 유효성을 가진다. $T\_l$ 이 경과하면 해당 메시지는 무효 처리된다.
* **Liveliness**: 노드 혹은 Publisher가 “활성(alive)” 상태임을 보증하는 방식이다. 자동 또는 수동으로 하트비트를 보내거나 특정 인터페이스를 통해 활성 상태를 확인한다.

#### Custom QoS 프로필 예시: XML 파일 사용

ROS2는 XML, YAML 파일 등을 통해 QoS 프로필을 정의하고, 런타임 혹은 노드 실행 시점에 불러올 수 있다. XML 파일 예시 구조는 다음과 같다.

```xml
<?xml version="1.0"?>
<profiles>
  <qos_profile>
    <name>custom_profile_example</name>
    <history>KEEP_LAST</history>
    <depth>10</depth>
    <reliability>RELIABLE</reliability>
    <durability>TRANSIENT_LOCAL</durability>
    <deadline>
      <sec>1</sec>
      <nsec>0</nsec>
    </deadline>
    <lifespan>
      <sec>5</sec>
      <nsec>0</nsec>
    </lifespan>
    <liveliness>AUTOMATIC</liveliness>
    <liveliness_lease_duration>
      <sec>2</sec>
      <nsec>0</nsec>
    </liveliness_lease_duration>
  </qos_profile>
</profiles>
```

이와 같은 XML 파일을 작성한 후, ROS2 노드 실행 시 아래와 같은 방식으로 해당 QoS 프로필을 로드해 사용할 수 있다.

```bash
# RMW_IMPLEMENTATION 또는 특정 DDS 벤더 설정이 필요할 수 있음
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
ros2 run my_package my_node --ros-args \
  --params-file /path/to/your_qos_profile.xml
```

일부 DDS 구현체는 QoS 프로필 XML 파일 로드를 기본적으로 지원하지만, 특정 환경에서는 parameter override 또는 노드 내부 코드에서 `rclcpp::QoS` 객체를 활용하여 직접 설정해야 한다.

#### C++ 코드에서 사용자 정의 QoS 적용

다음 예시는 C++ 노드에서 `rclcpp::QoS`를 통해 QoS 설정을 직접 적용하는 방법이다.

```cpp
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);
  auto node = rclcpp::Node::make_shared("custom_qos_node");

  // 사용자 정의 QoS 프로필 생성
  rclcpp::QoS custom_qos_profile(rclcpp::KeepLast(10));
  custom_qos_profile.reliability(rclcpp::ReliabilityPolicy::Reliable);
  custom_qos_profile.durability(rclcpp::DurabilityPolicy::TransientLocal);
  custom_qos_profile.deadline(rclcpp::Duration(1, 0));
  custom_qos_profile.lifespan(rclcpp::Duration(5, 0));
  custom_qos_profile.liveliness(rclcpp::LivelinessPolicy::Automatic);
  custom_qos_profile.liveliness_lease_duration(rclcpp::Duration(2, 0));

  // 해당 QoS 프로필을 적용하여 Publisher 생성
  auto pub = node->create_publisher<std_msgs::msg::String>("topic_name", custom_qos_profile);

  // 주기적으로 메시지 발행
  rclcpp::WallRate loop_rate(1);
  while (rclcpp::ok()) {
    auto msg = std_msgs::msg::String();
    msg.data = "Custom QoS Message";
    pub->publish(msg);
    rclcpp::spin_some(node);
    loop_rate.sleep();
  }

  rclcpp::shutdown();
  return 0;
}
```

이 예시에서는 `KeepLast(10)`을 통해 히스토리를 최대 10개의 메시지로 유지하며, `TransientLocal` 내구성을 사용하여 노드가 재시작되거나 늦게 구독을 시작해도 이전 메시지를 전달할 수 있도록 설정했다. 또한 `deadline`, `lifespan`, `liveliness` 등의 파라미터를 명시적으로 지정하여 특정 시간 제약 조건 및 노드 활성 상태 보장 방식을 세부적으로 설정한다.

#### Python 코드에서 사용자 정의 QoS 적용

Python에서 `rclpy`를 사용하는 경우 `QoSProfile` 객체를 통해 동일하게 정의할 수 있다. 대략적인 예시는 다음과 같다.

```python
import rclpy
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSDurabilityPolicy, QoSLivelinessPolicy, QoSHistoryPolicy
from std_msgs.msg import String

def main(args=None):
    rclpy.init(args=args)
    node = rclpy.create_node('custom_qos_node')

    qos_profile = QoSProfile(
        history=QoSHistoryPolicy.KEEP_LAST,
        depth=10,
        reliability=QoSReliabilityPolicy.RELIABLE,
        durability=QoSDurabilityPolicy.TRANSIENT_LOCAL
    )
    qos_profile.deadline.sec = 1
    qos_profile.lifespan.sec = 5
    qos_profile.liveliness = QoSLivelinessPolicy.AUTOMATIC
    qos_profile.liveliness_lease_duration.sec = 2

    publisher = node.create_publisher(String, 'topic_name', qos_profile)

    msg = String()
    msg.data = 'Hello with custom QoS!'
    publisher.publish(msg)

    rclpy.spin_once(node, timeout_sec=1)
    rclpy.shutdown()

if __name__ == '__main__':
    main()
```

이처럼 C++ 또는 Python 모두에서 QoS를 커스텀하여 정의할 수 있으며, 서로 다른 파라미터를 지정함으로써 노드 간 통신 품질 특성을 정밀하게 제어할 수 있다.

#### 여러 QoS 프로필을 동시에 사용하기

ROS2 애플리케이션에서 여러 Publisher/Subscriber가 서로 다른 QoS 요구 사항을 갖는 상황이 자주 발생한다. 예를 들어, 센서 데이터는 실시간성 위주로 Best Effort 신뢰성을 적용하고, 로깅(Log) 정보를 전송할 때는 Reliable 신뢰성을 적용할 수도 있다.

```cpp
// C++에서 서로 다른 QoS를 가진 퍼블리셔를 두 개 생성하는 예시

// 첫 번째 퍼블리셔(QoS1) -> 빠른 데이터 전송이 중요, 일부 손실 허용
rclcpp::QoS fast_qos(rclcpp::KeepLast(5));
fast_qos.reliability(rclcpp::ReliabilityPolicy::BestEffort);
fast_qos.durability(rclcpp::DurabilityPolicy::Volatile);

// 두 번째 퍼블리셔(QoS2) -> 신뢰성이 중요, 손실이 없어야 함
rclcpp::QoS reliable_qos(rclcpp::KeepLast(10));
reliable_qos.reliability(rclcpp::ReliabilityPolicy::Reliable);
reliable_qos.durability(rclcpp::DurabilityPolicy::TransientLocal);

auto fast_pub = node->create_publisher<std_msgs::msg::String>("fast_topic", fast_qos);
auto reliable_pub = node->create_publisher<std_msgs::msg::String>("reliable_topic", reliable_qos);
```

이처럼 QoS 프로필을 세분화하여, 목적에 맞게 동시에 여러 채널을 운영할 수 있다.

#### QoS Mismatch 발생 시 진단 방법

QoS 설정이 서로 다른 Publisher-Subscriber 간에는 연결이 성사되지 않거나, 연결되어도 메시지가 제대로 전달되지 않을 수 있다. 이를 **QoS Mismatch**라고 한다.

QoS Mismatch는 주로 다음과 같은 경우에 발생한다.

* Reliability가 Publisher는 Reliable, Subscriber는 Best Effort로 설정된 경우
* Durability가 Publisher는 TransientLocal, Subscriber는 Volatile로 설정된 경우
* ROS2(DDS) 레벨에서 지원하지 않는 극단적인 Deadline, Lifespan 값 설정

**진단 절차**

**노드 실행 로그 확인**: ROS2 노드 실행 시, QoS Mismatch와 관련된 경고(WARN) 로그가 찍히는지 모니터링한다.

**ROS CLI 툴 활용** Topic 리스트 및 정보 확인

```bash
ros2 topic list
ros2 topic info /topic_name
```

QoS 설정 확인

```bash
ros2 topic echo /topic_name --qos-durability vol
```

혹은 `--qos-reliability best_effort`

등으로 구독쪽 QoS를 강제로 지정해볼 수 있다.

이러한 CLI를 통해, Publisher 측 QoS 설정과 Subscriber 측 QoS 설정이 정확히 매칭되는지 점검한다.

1. **RQT 플러그인 사용**: `rqt_graph`, `rqt_topic` 플러그인 등을 통해 토픽 연결 상태를 시각적으로 확인하고, 메시지가 정상적으로 흐르는지 확인한다.
2. **DDS 로깅/진단 툴 활용**: CycloneDDS, Fast-DDS, ConnextDDS 등 DDS 구현체별로 제공하는 로깅 옵션이나 Wireshark DDS 플러그인을 통해 저수준 통신 상태를 분석할 수도 있다.

#### QoS 매개변수 간 의존성

QoS 파라미터는 독립적으로 설정할 수도 있지만, 일부 항목들은 상호 연관되어 있다. 예를 들어:

* **Reliability**가 `RELIABLE`이면, **Durability**가 `TRANSIENT_LOCAL` 또는 `VOLATILE`에 따라 메시지 전달 보장 범위가 달라진다.
* **Deadline**이나 **Lifespan**을 설정할 때, **Reliability**가 Best Effort이면 해당 시간 제약 충족을 DDS 레벨에서 완전히 보장하기 어렵다.
* **Liveliness**를 `MANUAL_BY_TOPIC`으로 설정하면, Publisher 쪽에서 주기적으로 liveliness heartbeat를 직접 전송하는 로직이 필요하다.

이를 수식으로 표현하자면, 메시지 전달의 만족도를 $S$라 하고, Reliability, Durability, Deadline, Lifespan 등이 서로 다른 함수 값에 기여한다고 볼 수 있다. 예시로 단순화하면,

$$
S = f(\mathrm{Reliability}, \mathrm{Durability}, \mathrm{Deadline}, \mathrm{Lifespan}, \dots)
$$

여기서 $f(\cdot)$는 각 QoS 파라미터 조합에 따라 정해지는 특정 지표(예: 지연 시간, 손실률 등)로 볼 수 있으며, 올바른 QoS 설정은 $S$를 최대화(높은 신뢰도)하거나 특정 시간 제약을 충족하도록 조정하는 과정을 의미한다.

#### QoS 프로필 기반 파라미터 동적 변경

ROS2에서 런타임 중에 QoS 프로필이 동적으로 바뀌는 것은 제한적이다. 일부 DDS 벤더나 ROS2 기능에서 동적 파라미터 리컨피규레이션을 지원하기도 하지만, 대체로 Publisher/Subscriber를 재생성해야 QoS 프로필 변경이 반영된다.

* **Dynamic Reconfigure**(ROS1 시절 기능)와 같은 방식의 완벽한 실시간 QoS 변화는 ROS2에서 기본 지원하지 않는다.
* 필요하다면 노드 재생성, Publisher/Subscriber 객체 재생성 등으로 새로운 QoS 프로필을 적용해야 한다.

#### QoS XML vs. Code 설정 비교

QoS 프로필은 XML, YAML, 또는 코드로 직접 정의하는 방식이 있다.

* **XML/YAML**: 런타임 전후로 손쉽게 조정 가능, DDS 벤더 별 프로필 파일을 공유하기 용이
* **코드 직접 설정**: 컴파일 시 QoS가 확정되므로, 의도치 않은 파라미터 변경 위험이 적다

일반적으로 **개발 단계**에서는 코드 내에서 QoS 설정을 명시해 빠른 반복 실험을 하고, **배포 단계**에서 XML/YAML 파일로 빼는 방식을 활용하기도 한다.

#### QoS XML 파일에서 여러 프로필 관리하기

ROS2에서 QoS XML 파일을 사용할 때, 하나의 파일 안에 다수의 QoS 프로필을 정의할 수 있다. 아래 예시는 여러 프로필을 한 번에 관리하는 방법이다.

```xml
<?xml version="1.0"?>
<profiles>
  <!-- 첫 번째 프로필: Reliable, KeepLast(10), 등 -->
  <qos_profile>
    <name>reliable_profile</name>
    <history>KEEP_LAST</history>
    <depth>10</depth>
    <reliability>RELIABLE</reliability>
    <durability>TRANSIENT_LOCAL</durability>
    <deadline>
      <sec>1</sec>
      <nsec>0</nsec>
    </deadline>
    <lifespan>
      <sec>5</sec>
      <nsec>0</nsec>
    </lifespan>
    <liveliness>AUTOMATIC</liveliness>
    <liveliness_lease_duration>
      <sec>2</sec>
      <nsec>0</nsec>
    </liveliness_lease_duration>
  </qos_profile>

  <!-- 두 번째 프로필: BestEffort, KeepLast(5), Volatile 등 -->
  <qos_profile>
    <name>fast_profile</name>
    <history>KEEP_LAST</history>
    <depth>5</depth>
    <reliability>BEST_EFFORT</reliability>
    <durability>VOLATILE</durability>
    <deadline>
      <sec>0</sec>
      <nsec>500000000</nsec> <!-- 0.5초 -->
    </deadline>
    <lifespan>
      <sec>2</sec>
      <nsec>0</nsec>
    </lifespan>
    <liveliness>AUTOMATIC</liveliness>
    <liveliness_lease_duration>
      <sec>1</sec>
      <nsec>0</nsec>
    </liveliness_lease_duration>
  </qos_profile>
</profiles>
```

ROS2 코드를 작성할 때, 특정 프로필을 불러오기 위해서는 DDS 구현체 별 혹은 RMW 계층에서 제공하는 방법을 사용해야 한다. 예를 들어 Fast-DDS를 사용할 경우에는 환경 변수를 활용하거나, XML 파일을 지정해주는 방식 등을 사용할 수 있다.

#### QoS 동작 확인을 위한 Topic 통계

ROS2는 메시지 지연, 손실률, 퍼블리셔-서브스크라이버 매칭 상태 등 통계 정보를 확인할 수 있는 메커니즘을 제공한다.

* **ros2 topic hz**: 토픽의 발행 주기(Hz) 측정
* **ros2 topic delay**: 메시지가 발행되어 구독되기까지의 지연 시간 측정

만약 QoS를 조정한 뒤, 예상보다 메시지 손실이 발생한다면, `ros2 topic hz`를 통해 실제 송신 빈도를 체크하고, `ros2 topic delay`로 지연을 확인할 수 있다. 이를 통해 Deadline 미준수, Reliability mismatch 등의 문제점을 진단할 수 있다.

#### DDS 벤더별 QoS 처리 차이

ROS2의 QoS는 DDS 계층에 크게 의존하므로, 사용 중인 DDS 벤더(Fast-DDS, CycloneDDS, RTI Connext 등)에 따라 미묘한 차이가 있을 수 있다.

* **Fast-DDS**: XML QoS 파일로 세부 파라미터를 많이 지정할 수 있으며, `fastrtps.xml` 형식을 통해 사용자 정의가 가능하다.
* **CycloneDDS**: cyclonedds.xml 파일 등을 통해 QoS 설정을 부분적으로 오버라이드할 수 있다.
* **RTI Connext**: 가장 다양한 QoS 파라미터를 지원하나, 상용 버전과 오픈 소스 버전 간 일부 차이가 있다.

따라서 사용자 정의 QoS 프로필을 작성할 때, ROS2 레벨에서 제공하는 `rclcpp::QoS` 혹은 `rclpy.QoSProfile`을 넘어 DDS 벤더별 고유 XML 설정이 필요한 경우에는 해당 벤더의 문서를 함께 참고해야 한다.

#### Advanced QoS 예시: Manual By Participant Liveliness

일부 실시간 시스템에서는 노드 생존 여부를 더 엄격히 모니터링해야 한다. 이때 Liveliness를 `MANUAL_BY_PARTICIPANT` 또는 `MANUAL_BY_TOPIC` 등으로 설정하고, Publisher 쪽에서 특정 주기로 “liveliness assertion”을 전송해야 한다.

```cpp
rclcpp::QoS qos(rclcpp::KeepLast(10));
qos.liveliness(rclcpp::LivelinessPolicy::ManualByParticipant);
qos.liveliness_lease_duration(std::chrono::seconds(2));
```

* **liveliness\_lease\_duration**: Publisher가 이 시간 내에 liveliness를 갱신하지 않으면, Subscriber 측에서 해당 Publisher가 더는 유효하지 않다고 판단한다.
* Publisher는 `rclcpp::PublisherOptions`를 통해 liveliness assertion 콜백 등을 설정하거나, 수동으로 DDS API 호출을 통해 liveliness 신호를 전송해야 한다.

#### QoS와 실시간(Real-Time) 시스템 고려 사항

실시간 애플리케이션에서 QoS 설정은 지연(latency), 스케줄링, CPU 자원 할당 등의 관점에서 주의 깊게 설계해야 한다.

* **Reliable** 설정 시, 재전송 메커니즘으로 인해 지연이 증가할 수 있다.
* **Best Effort** 사용 시, 메시지 손실 위험은 증가하지만 지연은 상대적으로 짧아질 수 있다.
* **Transient Local** 및 **KEEP\_ALL** 설정 시, 메시지를 버퍼링하거나 보존해야 하므로 메모리 사용량이 증가한다.

이를 수식으로 나타내면, 메시지 지연 시간을 $d$, 메시지 손실률을 $p$, CPU 사용률을 $c$라 할 때, QoS 파라미터에 따른 목표 함수를 구성할 수 있다. 예컨대

$$
\min \Bigl( \alpha d + \beta p + \gamma c \Bigr)
$$

와 같은 형태로, 여기서 $\alpha$, $\beta$, $\gamma$는 각 지표의 중요도를 의미한다(가중치). 그리고 QoS 파라미터에 따라 $d, p, c$가 달라지므로, 적절한 QoS 설정은 위 식을 최소화하는 쪽으로 모색하게 된다.

#### QoS Incompatibility 대비 전략

Publisher/Subscriber를 배포한 뒤, 예기치 못한 QoS 불일치로 인해 통신이 성사되지 않는 경우가 있을 수 있다. 이때를 대비해 다음과 같은 전략을 사용할 수 있다.

1. **Fallback QoS**: Subscriber 쪽에서 `SensorDataQoS()`, `ServicesQoS()`, `ParametersQoS()` 등 ROS2가 제공하는 대표 QoS 프로필을 먼저 시도하고, 매칭이 실패하면 별도 Custom QoS를 재시도한다.
2. **Multi-Subscription**: 하나의 노드에서 동일 토픽을 여러 QoS로 동시에 구독할 수도 있다. 예를 들어, `Best Effort` 구독기와 `Reliable` 구독기를 둘 다 만들어 두고, 메시지가 들어오는 쪽을 사용한다.
3. **QoS Negotiation(실험적)**: ROS2 Galactic 이후, QoS를 동적으로 협상하는 기능이 일부 연구되고 있으나, 아직은 제한적이다.

#### QoS 튜닝 시뮬레이션

물리적인 로봇 시스템에서 QoS를 즉시 적용하기 어려울 때, 시뮬레이터(Gazebo, Ignition)에서 노드를 띄워 테스트하는 방법을 사용할 수 있다. 시뮬레이터 내에서 트래픽 부하, 지연, 패킷 손실 등을 인위적으로 가중해가며 QoS 설정을 조정해볼 수 있다.

* 네트워크 레벨에서 tc (Traffic Control) 명령어 등을 사용해 지연, 패킷 손실률을 시뮬레이션

  ```bash
  # 100ms 지연 및 1% 패킷 손실율 적용 예시
  sudo tc qdisc add dev eth0 root netem delay 100ms loss 1%
  ```
* Gazebo/Ignition에서 여러 센서를 동시에 활성화해 메시지 폭주 상황을 만들거나, Subscriber가 처리량 한계를 넘어서는 워크로드를 주어 QoS 파라미터 조정 효과를 모니터링할 수 있다.

#### QoS 유닛 테스트/통합 테스트

여러 종류의 QoS 프로필을 작성했다면, 이를 자동화된 테스트로 검증하는 작업이 중요하다.

* **ROS2 Launch Testing**: `launch_testing` 패키지를 사용하여 노드 간 통신 시나리오를 스크립트로 구성한 뒤, QoS 파라미터에 따라 메시지가 잘 전달되는지 검사한다.
* **GTest/GMock**: C++에서 퍼블리셔-서브스크라이버 통합 테스트를 작성할 때, 원하는 QoS로 노드를 실행하고, 실제 메시지가 도착했는지 검증할 수 있다.

아래는 간단한 Pseudocode 예시이다.

```cpp
TEST(CustomQoSTest, ReliableDeliveryTest) {
  auto node = rclcpp::Node::make_shared("qos_test_node");
  rclcpp::QoS reliable_qos(rclcpp::KeepLast(10));
  reliable_qos.reliability(rclcpp::ReliabilityPolicy::Reliable);

  auto pub = node->create_publisher<std_msgs::msg::String>("test_topic", reliable_qos);

  bool msg_received = false;
  auto sub = node->create_subscription<std_msgs::msg::String>(
      "test_topic",
      reliable_qos,
      [&](const std_msgs::msg::String::SharedPtr msg) {
        if (msg->data == "Hello") {
          msg_received = true;
        }
      });

  // 메시지 발행
  pub->publish(std_msgs::msg::String().set__data("Hello"));

  // 일정 시간 rclcpp::spin_some 등으로 메시지 수신 대기
  for (int i = 0; i < 10; i++) {
    rclcpp::spin_some(node);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  EXPECT_TRUE(msg_received);
}
```

이 테스트 코드는 Reliable로 설정한 퍼블리셔와 서브스크라이버 간에 “Hello” 메시지가 정상적으로 도달하는지 확인한다.

#### QoS와 rosbag2

ROS2에서 데이터를 기록하고 재생하기 위한 툴인 **rosbag2**는 기본적으로 Subscriber 역할을 통해 토픽 데이터를 기록한다. 이때도 QoS 설정이 중요하다. 예를 들어, Publisher가 `TRANSIENT_LOCAL` 내구성을 사용할 때, `rosbag2` 측에서도 동일하게 `TRANSIENT_LOCAL` 구독을 해야 과거 메시지를 놓치지 않고 기록할 수 있다.

* 기본적으로 rosbag2는 **SensorDataQoS** 프로필을 사용한다(ROS 2 Foxy\~Galactic 기준).
* 최근 버전(특히 Humble 이후)에서는 rosbag2에 QoS 오버라이드 기능이 추가되어, YAML 파일이나 CLI 인자를 통해 원하는 QoS 설정으로 레코딩할 수 있다.

**rosbag2 QoS 오버라이드 예시**

```bash
ros2 bag record /some_topic --qos-profile-overrides-path my_qos_overrides.yaml
```

아래는 `my_qos_overrides.yaml`의 예시이다.

```yaml
# my_qos_overrides.yaml
profiles:
  some_topic:
    reliability: reliable
    durability: transient_local
    history: keep_last
    depth: 10
```

이렇게 하면 `/some_topic`을 구독할 때 `Reliable`, `Transient Local`, `KeepLast(10)` 등의 QoS 파라미터가 적용된다. 이를 통해 rosbag2가 과거 메시지까지 놓치지 않고 기록할 수 있게 된다.

#### QoS와 ROS2 서비스/액션

ROS2에서는 **토픽**뿐 아니라 **서비스**, **액션** 호출 시에도 DDS QoS가 적용될 수 있다. 다만 서비스와 액션은 내부적으로 요청(Request)과 응답(Response) 메시지를 주고받는 구조여서, QoS 매개변수가 일반 토픽과 약간 다를 수 있다.

* **서비스**: `ServiceQosProfile`이 존재해, 일반적으로는 신뢰성( Reliable )과 일시적 저장( Volatile )이 함께 사용된다. 시간 초과가 있으면 리턴되지 않는 등, 별도 타임아웃 매커니즘을 코드에서 처리해야 한다.
* **액션**: 피드백(Feedback), 목표(Goal), 결과(Result) 등 여러 방향의 통신 흐름이 존재하므로, 각 채널 별 QoS 세부 설정이 가능하다.

**서비스 QoS 예시**

```cpp
// C++ 예시
auto client = node->create_client<example_interfaces::srv::AddTwoInts>(
  "add_two_ints",
  rmw_qos_profile_services_default  // 또는 Custom QoS
);

// rmw_qos_profile_services_default 를 직접 커스텀하여 인자로 전달할 수도 있음
```

**액션 QoS 예시**

```cpp
// C++ 예시
rclcpp_action::Client<my_robot_interfaces::action::NavigateToPose>::Options options;
options.goal_service_qos = rclcpp::QoS(rclcpp::KeepLast(10))
                             .reliability(rclcpp::ReliabilityPolicy::Reliable);
options.result_service_qos = rclcpp::QoS(rclcpp::KeepLast(10));
options.cancel_service_qos = rclcpp::QoS(rclcpp::KeepLast(5));
options.feedback_sub_qos = rclcpp::QoS(rclcpp::KeepLast(10));

auto action_client = rclcpp_action::create_client<my_robot_interfaces::action::NavigateToPose>(
  node.get(),
  "navigate_to_pose",
  options
);
```

이처럼 액션의 각 통신 경로(Goal, Result, Feedback, Cancel)에 대해 별도 QoS를 지정할 수 있다.

#### QoS 보안(Security) 설정

DDS 보안을 활용할 경우, QoS 파일이나 DDS Vendor별 XML 설정을 통해 **암호화(Encryption)**, **인가(Authentication)**, **권한(Access Control)** 등을 설정할 수 있다.

* RTI Connext DDS Secure, Fast-DDS Secure 등 다양한 DDS 구현체에서 보안 기능을 지원한다.
* QoS 수준에서 암호화된 통신을 강제하면, 메시지 인가가 안 된 노드는 토픽에 접근할 수 없게 할 수도 있다.

실무에서는 ROS2 보안에 관련된 설정(예: SROS2)을 함께 적용해서 인증서, 키 등을 DDS 레벨에서 참조하게 만든다.

#### QoS와 멀티 로봇(Multi-Robot) 시나리오

여러 대의 로봇이 하나의 DDS 도메인에서 메시지를 주고받을 때, QoS 설정을 제대로 해두지 않으면 트래픽 충돌, 메시지 손실 등이 발생할 수 있다.

* 도메인 분할(Domain ID)을 통해 로봇 그룹을 분리하거나,
* 로봇 간 통신에만 `Reliable + KeepLast(N)`로 하고, 센서 토픽은 `Best Effort`로 하는 식으로 트래픽을 최적화할 수 있다.

서로 다른 로봇이 동일한 토픽 이름을 사용할 수도 있으므로, **네임스페이스** 또는 **Remapping**을 사용해 충돌을 방지해야 한다.

#### QoS 실험 자동화

CI/CD 환경에서 QoS 설정을 자동으로 검증하거나, 특정 시나리오에서 성능을 측정하기 위한 스크립트를 작성할 수 있다.

1. **Docker 컨테이너** 내부에서 여러 노드를 띄워 QoS 매개변수를 바꿔가며 테스트.
2. **Continuous Integration** 툴(Jenkins, GitLab CI 등)에서 `colcon test`를 통해 QoS 단위 테스트 수행.
3. 결과 로그(예: 메시지 유실률, 평균 지연)를 자동으로 수집해 그래프로 시각화.

이 과정을 통해 최적의 QoS 설정을 찾고, 의도치 않은 Regression(성능 저하)을 조기에 발견할 수 있다.

#### QoS Debugging Tips

* QoS 설정을 잘못한 것 같을 때, 가장 먼저 **Publisher/Subscriber 노드가 실제로 매칭**되었는지 확인한다(예: `rqt_graph`에서 연결선이 보이는지).
* `ros2 topic info /topic_name -v` 명령으로 해당 토픽의 퍼블리셔와 서브스크라이버 간 QoS를 구체적으로 확인한다.
* DDS 레벨 로깅(예: Fast-DDS의 `FASTRTPS_DEFAULT_PROFILES_FILE` 환경 변수 설정)을 활성화하면, QoS 불일치 원인을 좀 더 자세히 파악할 수 있다.
