|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: Node to Participant mapping |
| 4 | +permalink: articles/Node_to_Participant_mapping.html |
| 5 | +abstract: This article analyzes the performance implications of enforcing a one-to-one mapping between ROS Nodes and DDS Participants, and proposes alternative implementation approaches. |
| 6 | +author: '[Ivan Paunovic](https://github.com/ivanpauno)' |
| 7 | +published: true |
| 8 | +categories: Middleware |
| 9 | +--- |
| 10 | + |
| 11 | +{:toc} |
| 12 | + |
| 13 | +# {{ page.title }} |
| 14 | + |
| 15 | +<div class="abstract" markdown="1"> |
| 16 | +{{ page.abstract }} |
| 17 | +</div> |
| 18 | + |
| 19 | +Original Author: {{ page.author }} |
| 20 | + |
| 21 | +## Background |
| 22 | + |
| 23 | +### Node |
| 24 | + |
| 25 | +In ROS, a Node is an entity used to group other entities. |
| 26 | +For example: Publishers, Subscriptions, Servers, Clients. |
| 27 | +Nodes ease organization and code reuse, as they can be composed in different ways. |
| 28 | + |
| 29 | +### Domain Participant |
| 30 | + |
| 31 | +A Participant is a type of DDS entity. |
| 32 | +Participant also group other entities, like Publishers, Subscribers, Data Writters, Data Readers, etc. |
| 33 | +Creating more Participants adds overhead to an application: |
| 34 | + |
| 35 | +- Each Participant participates in discovery. |
| 36 | + Creating more than one Participant usually increases CPU usage and network IO load. |
| 37 | +- Each Participant keeps track of other DDS entities. |
| 38 | + Using more than one within a single process may result in data duplication. |
| 39 | +- Each Participant may create multiple threads for event handling, discovery, etc. |
| 40 | + The number of threads created per Participant depends on the DDS vendor (e.g.: [RTI Connext](https://community.rti.com/best-practices/create-few-domainParticipants-possible)). |
| 41 | + |
| 42 | +For those reasons, a Participant is a heavyweight entity. |
| 43 | + |
| 44 | +Note: This might actually depend on the DDS vendor, some of them share these resources between Participants (e.g. `OpenSplice`). |
| 45 | +Many DDS vendors, however, do not perform this kind of optimization (e.g.: `RTI Connext` and `Fast-RTPS`), and actually recommend creating just one Participant per process. |
| 46 | + |
| 47 | +### Context |
| 48 | + |
| 49 | +In ROS, a Context is the non-global state of an init-shutdown cycle. |
| 50 | +It also encapsulates shared state between Nodes and other entities. |
| 51 | +In most applications, there is only one ROS Context in a process. |
| 52 | + |
| 53 | +## Behavior pre-Foxy |
| 54 | + |
| 55 | +There is a one-to-one mapping between Nodes and DDS Participants. |
| 56 | +This simplified the original implementation, as DDS Participants provide many features equivalent to the ones of ROS Nodes. |
| 57 | +The drawback of this approach is the overhead that comes with creating many Participants. |
| 58 | +Furthermore, the maximum number of Domain Participants is rather small. |
| 59 | +For example, in [RTI Connext](https://community.rti.com/kb/what-maximum-number-Participants-domain) it is limited to 120 Participants per Domain. |
| 60 | + |
| 61 | +## Proposed approach |
| 62 | + |
| 63 | +The goal of this proposal is to improve overall performance by avoiding the creation of one Participant per Node. |
| 64 | +API changes will be avoided, if possible. |
| 65 | + |
| 66 | +### Mapping of DDS Participant to a ROS entity |
| 67 | + |
| 68 | +There are two alternatives, besides the one-to-one Node to Participant mapping used pre-Foxy: |
| 69 | +- Using one Participant per process. |
| 70 | +- Using one Participant per Context. |
| 71 | + |
| 72 | +The second approach is much more flexible, allowing more than one Participant in a single application for those that need it e.g. domain bridge applications. |
| 73 | +Thus, a one-to-one Participant to Context mapping was chosen. |
| 74 | + |
| 75 | +When multiple Nodes are running in a single process, there are different options for grouping them by - ranging from a separate context for each Node, over grouping a few Nodes in the same context, to using a single context for all Nodes. |
| 76 | +For most applications, only one Context is created. |
| 77 | + |
| 78 | +### Discovery information |
| 79 | + |
| 80 | +If a one-to-one Node to Participant mapping is not used, extra discovery information is needed to be able to match other entities to a Node e.g. Publishers, Subscriptions, etc. |
| 81 | +Several approaches can be used to share this information. |
| 82 | +The proposed approach uses a topic for it. |
| 83 | +Each Participant publishes a message with all the information needed to match an entity to a Node. |
| 84 | +The message structure is the following: |
| 85 | + |
| 86 | +* ParticipantInfo |
| 87 | + * gid |
| 88 | + * NodeInfo |
| 89 | + * Node namespace |
| 90 | + * Node name |
| 91 | + * Reader gid |
| 92 | + * writed gid |
| 93 | + |
| 94 | +When one entity is updated (e.g.: a Publisher is created or destroyed), a new message is sent. |
| 95 | + |
| 96 | +Identification of Clients and Servers happens according to the ROS conventions for their topic names (see [ |
| 97 | +Topic and Service name mapping to DDS](140_topic_and_service_name_mapping.md)). |
| 98 | + |
| 99 | +This topic is considered an implementation detail, and not all `rmw` implementations have to use it. |
| 100 | +Thus, all the necessary logic has to be in the rmw implementation itself or in an upstream package. |
| 101 | +Implementing this logic in `rcl` would make it part of the API, and not an implementation detail. |
| 102 | + |
| 103 | +To avoid code repetition, a common implementation of this logic is provided by the [rmw_dds_common](https://github.com/ros2/rmw_dds_common/) package. |
| 104 | + |
| 105 | +#### Details of the ROS discovery topic |
| 106 | + |
| 107 | +- topic name: `ros_discovery_info` |
| 108 | +- Writer qos: |
| 109 | + - durability: transient local |
| 110 | + - history: keep last |
| 111 | + - history depth: 1 |
| 112 | + - reliability: reliable |
| 113 | +- Reader qos: |
| 114 | + - durability: transient local |
| 115 | + - history: keep all |
| 116 | + - history depth: 1 |
| 117 | + - reliability: reliable |
| 118 | + |
| 119 | +## Other implications |
| 120 | + |
| 121 | +#### Security |
| 122 | + |
| 123 | +Previously, each Node could have different security artifacts. |
| 124 | +That was possible because each Node was mapped to one Participant. |
| 125 | +The new approach allows to specify different security artifacts for each process. |
| 126 | +For more details, see [ROS 2 Security Enclaves](ros2_security_enclaves.md). |
| 127 | + |
| 128 | +#### Ignore local publications option |
| 129 | + |
| 130 | +There is an `ignore_local_publications` option that can be set when [creating a Subscription](https://github.com/ros2/rmw/blob/2250b3eee645d90f9e9d6c96d71ce3aada9944f3/rmw/include/rmw/rmw.h#L517). |
| 131 | +That option avoids receiving messages from Publishers within the same Node. |
| 132 | +This wasn't implemented in all the rmw implementations (e.g.: [FastRTPS](https://github.com/ros2/rmw_fastrtps/blob/099f9eed9a0f581447405fbd877c6d3b15f1f26e/rmw_fastrtps_cpp/src/rmw_Subscription.cpp#L118)). |
| 133 | + |
| 134 | +After this change, implementing this feature will be less direct. |
| 135 | +Some extra logic needs to be added in order to identify from which Node a Publisher was created. |
| 136 | + |
| 137 | +## Alternative implementations |
| 138 | + |
| 139 | +### Using keyed topics |
| 140 | + |
| 141 | +Keyed topics could be used, with the Participant gid as the key. |
| 142 | +That would allow the Reader side of the topic to use a keep last, depth 1 history. |
| 143 | + |
| 144 | +### Using Participant/Writer/Reader userData QoS |
| 145 | + |
| 146 | +Instead of using a custom topic to share the extra ROS specific discovery information, a combination of Participant/Reader/Writer `userData` could be used: |
| 147 | + |
| 148 | +- Participant `userData`: list of Nodes created by the Participant (updated when Node created destroyed). |
| 149 | +- Reader user `userData`: Node name/namespace. |
| 150 | +- Writer user `userData`: Node name/namespace. |
| 151 | + |
| 152 | +That information would be enough to satisfy ROS required graph API. |
| 153 | +This mechanism would also preclude having a topic that has to be read and written by all Nodes, which is better from a security perspective. |
| 154 | + |
| 155 | +This alternative wasn't implemented because of lack of support in some of the currently supported DDS vendors. |
| 156 | + |
| 157 | +## Further work |
| 158 | + |
| 159 | +This optimization has been applied to `rmw_cyclonedds`, `rmw_fastrtps_cpp` and `rmw_fastrtps_dynamic_cpp`. |
| 160 | +Other DDS based `rmw` implementations, like `rmw_connext_cpp` could use the same approach. |
0 commit comments