Align ROS 2 QoS Policies to Stop Silent Message Loss
ROS 2 QoS policies prevent silent message loss; aligning reliability and depth stops camera‑perception communication failures.
30 Nov 2025, 10:14 UTC

The silent loss problem
Imagine a camera node publishing 30‑hz images on /camera/image. A perception node subscribes with a different reliability setting, yet no error appears. The image stream appears to stop, but the nodes keep running. This is a classic silent message loss caused by a QoS mismatch.
Why it looks like a bug
The ROS 2 middleware uses DDS, which does not automatically negotiate reliability. If a BEST_EFFORT publisher talks to a RELIABLE subscriber, the connection is established but the publisher never waits for acknowledgments. When the subscriber is slow, its queue overflows and the publisher discards samples, resulting in data that never arrives.
ROS 2 QoS policies are explicit
In ROS 2 the middleware uses DDS, which makes reliability, durability, history and depth a per‑topic QoS policy that you set when creating a publisher or subscriber. The four main reliability kinds are:
- BEST_EFFORT: fire‑and‑forget, no acknowledgment.
- RELIABLE: sends acknowledgments and retransmits until delivered.
Other policies such as KEEP_ALL or KEEP_LAST control how many messages are retained when the queue is full, and TRANSIENT_LOCAL versus VOLATILE determine whether late‑joining subscribers receive previous samples.
Worked example: camera and perception nodes
Suppose the camera node is created with the default BEST_EFFORT profile and a depth of 10:
rclpy.init()
pub = node.create_publisher(Image, '/camera/image',
qos_profile=rclpy.qos.QoSProfile(
reliability=rclpy.qos.ReliabilityPolicy.BEST_EFFORT,
history=rclpy.qos.HistoryPolicy.KEEP_ALL,
depth=10))
The perception node requests a reliable subscription:
sub = node.create_subscription(Image, '/camera/image',
callback=callback,
qos_profile=rclpy.qos.QoSProfile(
reliability=rclpy.qos.ReliabilityPolicy.RELIABLE,
history=rclpy.qos.HistoryPolicy.KEEP_LAST,
depth=5))
Because reliability differs, the two endpoints cannot exchange data reliably. The perception node will see a constantly empty queue, which appears as a software bug.
Fixing the problem is as simple as aligning the reliability and increasing the depth to match the worst‑case processing time:
pub = node.create_publisher(Image, '/camera/image',
qos_profile=rclpy.qos.QoSProfile(
reliability=rclpy.qos.ReliabilityPolicy.RELIABLE,
history=rclpy.qos.HistoryPolicy.KEEP_LAST,
depth=30))
After the change, run ros2 topic info -v /camera/image on both nodes. The output should show reliability: RELIABLE and a depth that is large enough for the 33 ms interval between frames.
Trade‑offs, limitations and checklist
Reliable QoS adds acknowledgment traffic, which can increase latency and cause head‑of‑line blocking on congested networks. Use it only for data where loss is unacceptable, such as control commands or configuration updates.
Depth must be sized to the subscriber’s maximum processing time. A KEEP_LAST depth of 1 is insufficient if the consumer needs more than one sample per cycle; the queue will drop older data even under RELIABLE semantics.
Different DDS vendors (Fast DDS, Cyclone DDS) implement the QoS specifications with subtle variations, so default profiles may differ between ROS 2 distributions. Always verify the active RMW implementation with ros2 doctor.
Actionable checklist:
- Run
ros2 topic info -v <topic>for every endpoint and record the effective reliability and depth. - Document the chosen QoS profile per topic in a configuration file or README.
- Add a CI step that parses
ros2 topic info -voutput and fails when a publisher’s reliability does not match a subscriber’s. - When changing QoS on a live system, stop the affected nodes, modify the launch file or parameters, then restart them – the policy is set at creation time.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.