QoS (Quality of Service) profiles control how DDS handles each topic — whether it guarantees delivery, how many messages it queues, whether new subscribers get the latest message on subscribe. Wrong QoS doesn’t always cause obvious bugs; it causes subtle ones (a subscriber receiving stale data, a publisher getting throttled by an over-eager retry).
The three knobs that matter
Reliability:
BEST_EFFORT— fire and forget, no retransmits. Right for high-frequency continuous data where the next message obsoletes the previous one (sensor streams, position setpoints).RELIABLE— DDS retransmits if delivery fails. Right for one-shot or state-change messages where every one matters (/mavros/state,/exploration/done).
Durability:
VOLATILE— only deliver messages published after the subscriber connects. Default for most topics.TRANSIENT_LOCAL— latched: late subscribers get the most recent message even if it was published before they joined. Right for static state (map data, configuration that’s published once at startup).
History + depth:
KEEP_LAST+depth=N— keep the last N messages queued for slow subscribers.KEEP_ALL— keep everything (memory-bounded by other DDS settings).
Recommended table
| Topic kind | Reliability | Durability | Depth |
|---|---|---|---|
| Continuous sensor (TF-Luna 100 Hz) | BEST_EFFORT | VOLATILE | 1 |
| Single scans (LaserScan after sweep) | RELIABLE | VOLATILE | 5 |
| Position setpoint to MAVROS | BEST_EFFORT | VOLATILE | 1 |
/mavros/state |
RELIABLE | VOLATILE | 10 |
/map (latched) |
RELIABLE | TRANSIENT_LOCAL | 1 |
/exploration/done |
RELIABLE | TRANSIENT_LOCAL | 1 |
Python boilerplate
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy, HistoryPolicy
sensor_qos = QoSProfile(
reliability=ReliabilityPolicy.BEST_EFFORT,
durability=DurabilityPolicy.VOLATILE,
history=HistoryPolicy.KEEP_LAST,
depth=1)
state_qos = QoSProfile(
reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.VOLATILE,
history=HistoryPolicy.KEEP_LAST,
depth=10)
# Then:
self.create_subscription(Range, '/drone/tf_luna_down', cb, sensor_qos)
self.create_subscription(State, '/mavros/state', cb, state_qos)
Common pitfall — QoS mismatch
If publisher and subscriber have incompatible QoS (e.g., publisher is BEST_EFFORT, subscriber is RELIABLE), they’ll silently fail to communicate. ros2 topic info <topic> --verbose lists each endpoint’s QoS — that’s the diagnostic when “I see the topic but I get no messages.”
Where to go next
- Environment variables — the host-level config side
- ROS 2 latency tuning — the analytical context
- Settings hub — sibling pages