분산 시스템을 설계할 때 마주하는 가장 근본적인 과제 중 하나는 보장되고, 순서가 있으며, 정확히 한 번(exactly-once)만 전달되는 메시징을 구현하는 것입니다. 이 문제는 특히 기반 전송 프로토콜이 UDP처럼 본질적으로 비연결성이고 비신뢰적일 때 더욱 복잡해집니다.1 이러한 배경 속에서 정교한 미들웨어 솔루션의 필요성이 대두됩니다.
데이터 분산 서비스(Data Distribution Service, DDS)는 단순한 메시징 라이브러리를 넘어, 실시간 분산 시스템 구축을 위한 표준화된 데이터 중심 패러다임을 제시합니다.1 DDS의 핵심 역량은 두 가지 주요 메커니즘을 통해 복잡한 네트워크 프로그래밍을 추상화하는 데 있습니다. 첫째는 실시간 발행-구독(Real-Time Publish-Subscribe, RTPS) 와이어 프로토콜이며, 둘째는 풍부하고 설정 가능한 서비스 품질(Quality of Service, QoS) 프레임워크입니다.3
본 보고서의 목적은 외부 메시지 큐(예: Kafka, RabbitMQ)에 저장된 데이터를 DDS 도메인으로 안정적으로 전송하는 “게이트웨이” 또는 “브리지” 애플리케이션을 설계하고 구현하기 위한 포괄적인 아키텍처 청사진을 제공하는 것입니다. 이 아키텍처는 UDP 전송 중 데이터 유실이나 중복을 방지하여, 시스템 경계를 넘어선 완벽한 단일 전송(exactly-once semantics)을 달성하는 것을 목표로 합니다.
DDS 신뢰성의 이면에는 잘 정의된 프로토콜 엔지니어링 솔루션이 존재합니다. 이 섹션에서는 그 메커니즘을 심층적으로 분석합니다.
DDS는 표준 API이자 데이터 중심 모델이며 5, RTPS는 이를 구현하는 표준화된 와이어 프로토콜입니다.4 DDS가 UDP 위에 구축된 이유는 실시간 성능에 필수적인 낮은 지연 시간, 적은 오버헤드, 그리고 멀티캐스트 기능을 활용하기 위함입니다.5 그 후 DDS는 자체 신뢰성 메커니즘을 상위에 계층화하여, 성능과 선택적 신뢰성이라는 두 가지 장점을 모두 제공합니다.2
이러한 설계는 DDS의 신뢰성이 UDP의 기능이 아니라, 전적으로 RTPS 프로토콜 계층 내에서 구현된다는 것을 의미합니다.8 이는 네트워크 엔지니어링에서 고전적인 설계 패턴(예: IP 위의 TCP, UDP 위의 QUIC 10)으로, 관심사의 분리를 통해 각 계층의 장점을 극대화하는 핵심적인 아키텍처 강점입니다.
신뢰성은 RTPS 프로토콜 계층 내에서 상태를 가진 ACK/NACK 기반 프로토콜을 구현함으로써 달성됩니다. 이 메커니즘은 다음과 같은 핵심 요소들로 구성됩니다.
DataWriter가 발행하는 모든 데이터 샘플에는 단조롭게 증가하는 64비트 시퀀스 번호가 할당됩니다.11 이 SN은 DataReader가 패킷 손실(SN 시퀀스의 공백)을 감지하고 데이터 중복(이미 처리된 SN 무시)을 방지하는 근본적인 메커니즘입니다. 예를 들어, DATA(value, sequenceNum) 형태의 메시지는 특정 값과 고유한 SN을 연관시킵니다.11 이는 실시간 전송 프로토콜(RTP)과 같은 다른 프로토콜에서도 흔히 볼 수 있는 패턴입니다.12
DataWriter는 주기적으로 매칭된 DataReader들에게 HEARTBEAT 메시지를 전송합니다.8 이 메시지는 DataWriter의 히스토리 캐시에 사용 가능한 시퀀스 번호의 범위를 알립니다(예: HB(1-3)는 SN 1, 2, 3이 사용 가능함을 의미).11 이 메커니즘은 수신자가 자신의 상태를 확인하고 데이터가 누락된 경우 반응하도록 유도하는 사전 예방적 조치이며, 동시에 DataWriter의 활성 상태를 알리는 역할도 합니다.
HEARTBEAT를 수신한 DataReader는 DataWriter가 제공하는 SN 범위와 자신이 수신한 SN을 비교합니다. 모든 데이터가 존재하면 긍정적 확인 응답(ACK)을 보낼 수 있습니다. 만약 공백이 있다면, 구체적으로 누락된 시퀀스 번호의 비트맵을 포함하는 부정적 확인 응답(NACK) 메시지를 보냅니다.11 예를 들어,
ACKNACK(4)는 SN 1, 2, 3은 수신되었지만 4가 누락되었음을 의미합니다.11
NACK를 수신한 DataWriter는 자신의 히스토리 캐시에서 요청된 누락 샘플만 재전송합니다. 이러한 표적화된 재전송은 전체 윈도우를 재전송하는 것보다 훨씬 효율적입니다.
이러한 재전송 메커니즘은 필연적으로 DataWriter가 잠재적인 재전송을 위해 샘플을 메모리에 저장해야 함을 시사합니다. 이 메모리가 바로 히스토리 캐시(History Cache)이며, 이는 “신뢰성”이 메모리와 CPU 측면에서 리소스 비용을 수반함을 명확히 보여줍니다. 이 비용을 관리하는 방법은 다음 섹션의 QoS 설정에서 다루어집니다.
다이어그램 1: RTPS 신뢰성 프로토콜 시퀀스 (데이터 손실 시)
아래 다이어그램은 네트워크에서 데이터 패킷이 손실되었을 때 RTPS 프로토콜이 어떻게 신뢰성을 보장하는지 보여줍니다.
sequenceDiagram
participant DataWriter
participant DataReader
DataWriter ->> DataReader: DATA(A, SN=1) [X - Packet Lost]
DataWriter ->> DataReader: DATA(B, SN=2)
DataWriter ->> DataReader: HEARTBEAT(SNs 1-2)
activate DataReader
Note over DataReader: [수신] DATA(B, SN=2)<br/>[수신] HEARTBEAT(1-2)<br/>[감지] SN=1 누락
DataReader -->> DataWriter: ACKNACK(Missing=1)
deactivate DataReader
activate DataWriter
Note over DataWriter: [수신] ACKNACK(Missing=1)<br/>[동작] 히스토리 캐시에서 SN=1 재전송
DataWriter ->> DataReader: DATA(A, SN=1) [재전송]
deactivate DataWriter
activate DataReader
Note over DataReader: [수신] DATA(A, SN=1)<br/>[동작] SN=1, SN=2 순서대로 처리
DataReader -->> DataWriter: ACKNACK(Ack up to 2)
deactivate DataReader
activate DataWriter
Note over DataWriter: [수신] ACK(up to 2)<br/>[동작] SN 1, 2를 히스토리에서 제거 가능
deactivate DataWriter
이 섹션에서는 프로토콜 이론을 실제 구성 가능한 정책으로 전환하는 방법을 설명합니다.
QoS 정책은 개발자가 DDS 미들웨어의 동작을 제어하여 특정 사용 사례에 맞게 통신을 조정하는 메커니즘입니다.3 DDS는 22개 이상의 표준 QoS 정책을 제공하며 14, 연결이 성립되기 위해서는 DataReader가 요청하는 QoS가 DataWriter가 제공하는 QoS와 호환되어야 한다는 요청-제공(Request-Offered, RxO) 의미론을 따릅니다.15
“완벽한 단일 전송”을 달성하는 것은 단일 설정이 아니라, 네 가지 개별 QoS 정책(Reliability, History, Durability, ResourceLimits) 간의 규약 계약의 결과물입니다. 이 중 하나라도 잘못 구성하면 보증이 미묘하게 깨질 수 있습니다.
이것은 신뢰성을 위한 마스터 스위치입니다.
BEST_EFFORT: “쏘고 잊어버리는(Fire and forget)” 방식으로, 원시 UDP와 유사합니다. 최신 값이 단일 손실 샘플보다 더 중요한 고주파 센서 데이터에 적합합니다.18
RELIABLE: 섹션 1에서 설명한 RTPS ACK/NACK 메커니즘을 활성화하여 전달을 보장합니다. 명령, 상태 전환, 금융 거래 등 손실이 허용되지 않는 모든 데이터에 필수적입니다.18 XML 및 API에서는
RELIABLE_RELIABILITY_QOS로 지정됩니다.21
이 정책은 DataWriter의 히스토리 캐시, 즉 재전송을 위한 메모리를 제어합니다.
KEEP_LAST (depth=N): 캐시는 크기 N의 순환 버퍼처럼 작동합니다. 가득 차면 새 샘플이 가장 오래된 샘플을 덮어씁니다. 가장 오래된 샘플이 아직 확인 응답을 받지 못했더라도 덮어씁니다. 이는 엄격한 신뢰성을 깨뜨릴 수 있습니다.17KEEP_ALL: 캐시는 모든 샘플을 모든 신뢰성 있는 수신자가 확인 응답하거나 리소스 한계에 도달할 때까지 저장합니다. 이것이 진정한 무손실 통신에 필수적입니다.15이 정책은 데이터가 작성된 후에 참여하거나 재시작하는 DataReader(지연 참여자)에게 데이터가 제공되도록 보장합니다.
VOLATILE: 지속성이 없습니다. 지연 참여자는 오래된 데이터를 놓칩니다.TRANSIENT_LOCAL: DataWriter는 지연 참여자에게 제공하기 위해 데이터를 자신의 메모리(히스토리 캐시)에 보관합니다. 이는 DataReader(또는 게이트웨이)가 재시작된 후 상태를 복구하는 데 매우 중요합니다.20Reliability)이 반드시 RELIABLE로 설정되어야 합니다.26Durability.kind = TRANSIENT_LOCAL 설정은 DataWriter의 히스토리 캐시를 직접 재사용합니다. 즉, 캐시의 역할을 비행 중인 미확인 데이터를 위한 임시 버퍼에서 지연 참여자를 위해 확인된 데이터를 장기간 보관하는 캐시로 변경합니다. 이는 Durability가 이 모드에서 별도의 메모리 저장소가 아니라 기존 History 캐시에 적용되는 규칙임을 명확히 하며, ResourceLimits 구성의 중요성을 더욱 강조합니다.
이 정책은 히스토리 캐시에 대한 물리적 메모리 경계를 설정합니다.
설정: max_samples, max_instances, max_samples_per_instance와 같은 값들은 KEEP_ALL 히스토리의 메모리 사용량을 제한하여 무한한 메모리 증가를 방지합니다.15
History.depth는 ResourceLimits.max_samples_per_instance보다 작거나 같아야 하는 등 일관성 규칙이 적용됩니다.24
이 네 가지 기둥을 종합하여 일관된 전략을 수립할 수 있습니다.
Reliability.kind = RELIABLE과 History.kind = KEEP_ALL의 조합은 필수적입니다. 이는 DataWriter가 모든 것을 전송하려고 시도하고 그렇게 할 메모리를 확보하도록 보장합니다.27Durability.kind = TRANSIENT_LOCAL을 추가해야 합니다. 이는 DataWriter가 잠재적인 지연 참여자를 위해 확인된 데이터를 보관하도록 지시합니다.24ResourceLimits는 예상 데이터 볼륨에 충분한 메모리를 제공하되, 무한한 메모리 증가를 막기 위해 상한선을 설정해야 합니다.이 구성의 중요한 결과는 “블로킹(blocking)” 동작입니다. 만약 DataReader가 실패하여 확인 응답을 중단하면, RELIABLE/KEEP_ALL로 설정된 DataWriter는 결국 ResourceLimits를 채우게 되고, write() 호출은 결코 오지 않을 확인 응답을 기다리며 블로킹됩니다.15 이때
Reliability.max_blocking_time QoS 27는 무기한 블로킹 대신 쓰기 작업이 시간 초과되어 오류를 반환하도록 하는 중요한 안전 밸브 역할을 합니다.
| 표 1: 보장된 전달을 위한 핵심 QoS 정책 |
|---|
| 정책 이름 |
ReliabilityQosPolicy |
HistoryQosPolicy |
DurabilityQosPolicy |
ResourceLimitsQosPolicy |
이 섹션에서는 DDS 내부에서 시스템 수준 아키텍처로 초점을 이동합니다.
전통적인 메시지 큐(Kafka/RabbitMQ 등)와 DDS의 근본적인 패러다임은 서로 다릅니다.
이러한 패러다임은 근본적으로 호환되지 않으므로, 이들 사이를 중재하기 위한 전용 “게이트웨이” 또는 “브리지” 애플리케이션이 필요합니다.29
| 표 2: 아키텍처 비교: DDS 대 전통적 메시지 큐 |
|---|
| 기능 |
| 아키텍처 |
| 데이터 모델 |
| 발견(Discovery) |
| 내구성 모델 |
| 통신 패턴 |
게이트웨이의 핵심 책임은 다음과 같습니다.
DataWriter 역할을 합니다.이러한 게이트웨이는 단일 장애점이 될 수 있으므로, 다중 인스턴스(액티브-패시브, 파티션 소비를 통한 액티브-액티브) 실행 전략과 그에 따른 상태 관리의 복잡성을 고려해야 합니다.
다이어그램 2: 외부 큐-DDS 게이트웨이 아키텍처
이 다이어그램은 외부 메시지 큐의 데이터를 DDS 도메인으로 안정적으로 전달하는 게이트웨이 애플리케이션의 내부 구성 요소와 데이터 흐름을 보여줍니다.
graph TD
subgraph "외부 시스템"
A
end
subgraph "게이트웨이 애플리케이션"
B1["Queue Consumer"]
B2
B3
B4
end
subgraph "DDS 시스템"
C1
end
A -- "1 메시지 읽기<br/>(오프셋 커밋 안 함)" --> B1
B1 --> B2
B2 -- "4 DDS 샘플로 변환" --> B4
B4 -- "5 DDS에 발행" --> C1
C1 -- "(ACK/NACK)" --> B4
B4 -- "5 DDS ACK 확인" --> B3
B3 -- "6 오프셋 커밋 신호" --> B1
문제의 핵심은 종단 간 흐름입니다. 신뢰성의 과제는 네트워크 전송 계층(RTPS로 해결됨)에서 애플리케이션 계층, 특히 게이트웨이의 상태 관리 로직으로 이동합니다. 게이트웨이 자체가 무결성이 가장 중요한 상태 저장 구성 요소가 되는 것입니다.
핵심 로직은 트랜잭션 방식의 읽기-처리-쓰기 루프를 구현하는 것입니다.
DataWriter를 사용하여 각 메시지를 DDS에 발행합니다.DataReader에 의해 완전히 확인될 때까지 기다립니다. DataWriter의 wait_for_acknowledgments()와 같은 API 호출이 여기서 사용됩니다.다이어그램 3: 종단 간 완벽한 단일 전송(Exactly-Once) 시퀀스
이 시퀀스 다이어그램은 게이트웨이가 충돌하더라도 데이터 중복이나 유실 없이 메시지를 전달하는 과정을 보여줍니다. 핵심은 DDS 전송이 완전히 확인된 후에만 소스 큐의 오프셋을 커밋하는 것입니다.
graph TD
subgraph "외부 시스템"
A
end
subgraph "게이트웨이 애플리케이션"
B1["Queue Consumer"]
B2
B3
B4
end
subgraph "DDS 시스템"
C1
end
A -- "1. 메시지 읽기<br/>(오프셋 커밋 안 함)" --> B1
B1 --> B2
B2 -- "4. DDS 샘플로 변환" --> B4
B4 -- "5. DDS에 발행" --> C1
C1 -- "(ACK/NACK)" --> B4
B4 -- "5. DDS ACK 확인" --> B3
B3 -- "6. 오프셋 커밋 신호" --> B1
이러한 접근 방식은 간단한 장애 시나리오를 처리합니다. 예를 들어, 게이트웨이가 오프셋을 커밋하기 전에 충돌하면, 재시작 시 동일한 메시지 배치를 다시 읽게 됩니다. DDS DataReader는 이미 이 메시지를 수신했으므로(재시작했다면 TRANSIENT_LOCAL 내구성 덕분에), 시퀀스 번호를 기반으로 중복을 폐기합니다.
소스 큐의 선택은 게이트웨이 설계와 장애 복구 능력에 지대한 영향을 미칩니다. Kafka의 재생 가능한 영구 로그는 RabbitMQ의 소비 후 삭제되는 큐 모델보다 일반적으로 완벽한 단일 전송 의미론을 달성하는 데 더 우수합니다.32 Kafka를 사용하는 게이트웨이는 중지 후 재시작하여 마지막으로 커밋된 오프셋부터 간단히 읽기를 재개할 수 있습니다.
이 섹션에서는 코드와 구성을 통해 구체적인 “방법”을 제공합니다.
다음은 게이트웨이 애플리케이션의 핵심 로직을 보여주는 상세한 C++ 의사 코드입니다.
// 의사 코드: DDS-Queue Gateway Main Loop
// 1. 초기화
QueueConsumer queue_consumer = initialize_queue_consumer("kafka_broker", "source_topic");
DDS_Publisher dds_publisher = initialize_dds_publisher("DDS_Domain", "target_topic", "ExactlyOnceQoSProfile");
// 2. 메인 루프
while (application_is_running) {
try {
// 2.1. 큐에서 메시지 폴링 (오프셋 자동 커밋 비활성화)
vector<QueueMessage> messages = queue_consumer.poll(timeout);
if (messages.empty()) {
continue;
}
// 2.2. 메시지 변환 및 DDS에 발행
for (const auto& msg : messages) {
DDS_Sample sample = translate_to_dds_sample(msg);
dds_publisher.write(sample);
}
// 2.3. 모든 DDS 샘플이 확인될 때까지 대기
// 이는 모든 신뢰성 있는 DataReader가 데이터를 수신했음을 보장합니다.
// max_blocking_time QoS에 따라 타임아웃이 발생할 수 있습니다.
ReturnCode_t ret = dds_publisher.wait_for_acknowledgments(dds_timeout);
if (ret!= DDS::RETCODE_OK) {
// 타임아웃 또는 오류 처리: 재시도 또는 로깅 후 루프 계속
log_error("DDS acknowledgment failed or timed out.");
continue; // 오프셋을 커밋하지 않고 다음 폴링으로 넘어감
}
// 2.4. DDS 쓰기가 성공적으로 확인된 후에만 큐 오프셋 커밋
queue_consumer.commit_offsets();
} catch (const QueueException& e) {
log_error("Queue consumer error: " + e.what());
// 복구 로직
} catch (const DDSException& e) {
log_error("DDS publisher error: " + e.what());
// 복구 로직
}
}
// 3. 정리
dds_publisher.shutdown();
queue_consumer.close();
이 로직은 37와 29에서 설명된 개념의 실제 구현이며, C++ 구조는 36(DDS 측) 및 38(개념적 Kafka 로직)과 같은 예제에서 영감을 얻을 수 있습니다.
다음은 프로덕션 환경에 바로 사용할 수 있는 완전한 USER_QOS_PROFILES.xml 파일입니다. 이 프로파일은 NDDS_QOS_PROFILES 환경 변수를 통해 로드되어야 합니다.40
<dds xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://community.rti.com/schema/6.1.0/rti_dds_qos_profiles.xsd">
<qos_library name="GatewayLibrary">
<qos_profile name="ExactlyOnceProfile" is_default_qos="true">
<datawriter_qos>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
<max_blocking_time>
<sec>10</sec>
</max_blocking_time>
</reliability>
<history>
<kind>KEEP_ALL_HISTORY_QOS</kind>
</history>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
<resource_limits>
<max_samples>5000</max_samples>
<max_instances>100</max_instances>
<max_samples_per_instance>50</max_samples_per_instance>
</resource_limits>
<writer_data_lifecycle>
<autodispose_unregistered_instances>false</autodispose_unregistered_instances>
</writer_data_lifecycle>
</datawriter_qos>
<datareader_qos>
<reliability>
<kind>RELIABLE_RELIABILITY_QOS</kind>
</reliability>
<history>
<kind>KEEP_ALL_HISTORY_QOS</kind>
</history>
<durability>
<kind>TRANSIENT_LOCAL_DURABILITY_QOS</kind>
</durability>
</datareader_qos>
</qos_profile>
</qos_library>
</dds>
이 프로파일은 21에 표시된 프로파일을 정제하고 17의 모범 사례를 통합한 것입니다.
Fast DDS에 대한 동등한 XML 프로파일 및 C++ API 설정은 다음과 같습니다.
// C++ QoS setup for eProsima Fast DDS
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>
//...
eprosima::fastdds::dds::DataWriterQos wqos;
// Reliability
wqos.reliability().kind = eprosima::fastdds::dds::RELIABLE_RELIABILITY_QOS;
wqos.reliability().max_blocking_time = {10, 0}; // 10 seconds
// Durability
wqos.durability().kind = eprosima::fastdds::dds::TRANSIENT_LOCAL_DURABILITY_QOS;
// History
wqos.history().kind = eprosima::fastdds::dds::KEEP_ALL_HISTORY_QOS;
// Resource Limits
wqos.resource_limits().max_samples = 5000;
wqos.resource_limits().max_instances = 100;
wqos.resource_limits().max_samples_per_instance = 50;
//... DataWriter 생성 시 wqos 사용
// DataWriter* writer = publisher->create_datawriter(topic, wqos, &listener);
이 구성은 Fast DDS 문서 22을 기반으로 합니다.
TRANSIENT_LOCAL을 사용할 때 RELIABLE을 설정하는 것을 잊은 사용자 사례 26는 중요한 교훈을 줍니다.
벤더마다 기본 동작, 특정 QoS 확장(예: Connext의 <rtps_reliable_writer> 21 내 프로토콜 튜닝 옵션), 성능 특성에 차이가 있을 수 있습니다. 예를 들어, 기본 검색 메커니즘이나 리소스 관리 방식이 다를 수 있으므로, 특정 벤더의 문서를 참조하여 구현을 미세 조정하는 것이 중요합니다.
이 섹션에서는 프로덕션 환경에서 솔루션을 견고하고 성능이 뛰어나게 만들기 위한 고급 주제를 다룹니다.
신뢰성 있는 시스템은 빠르기도 해야 합니다. 성능 튜닝은 OS 커널에서부터 네트워크 하드웨어, 미들웨어 구성에 이르기까지 전체 스택에 걸친 문제입니다. 어느 한 계층의 병목 현상이 전체 시스템을 마비시킬 수 있습니다.
net.core.rmem_max)를 늘립니다.43DataWriter가 네트워크나 느린 DataReader를 압도하는 것을 방지합니다.44DataWriter의 전송 윈도우를 조정하여 처리량과 메모리 사용량의 균형을 맞춥니다.21DataWriter를 구성하여 효율성을 향상시킵니다.복잡한 DDS 구성은 강력하지만, 디버깅 및 모니터링을 위한 견고한 도구를 사치가 아닌 필수품으로 만듭니다. RTI Admin Console, Wireshark, 상세 로깅과 같은 도구 없이는 효과적인 운영이 불가능합니다.48
| 표 3: 신뢰성 있는 DDS 통신 문제 해결 체크리스트 |
|---|
| 증상 |
| 엔드포인트가 서로를 발견하지 못함 |
DataWriter가 블로킹되거나 타임아웃 발생 |
RELIABLE QoS에도 불구하고 데이터가 손실됨 |
| 재시작 시 오래되거나 중복된 데이터를 수신함 |
본 보고서는 외부 큐와 DDS 간의 완벽한 단일 전송을 달성하기 위한 포괄적인 프레임워크를 제시했습니다. 이 솔루션의 핵심 원칙은 세 가지로 요약할 수 있습니다.
Reliability, History, Durability, ResourceLimits의 조합을 통해 애플리케이션별 보증을 강제하는 상위 수준의 제어를 제공합니다.DDS는 표준 기반 3 고성능 30 솔루션으로서 복잡한 실시간 통합 과제에 대한 강력한 해답을 제시합니다. 초기 학습 곡선과 구성이 다소 가파를 수 있지만 51, 그 결과로 얻어지는 시스템은 미션 크리티컬 애플리케이션에 대한 투자를 정당화할 만큼 매우 견고하고, 확장 가능하며, 유지보수하기 쉽습니다.
| 03장 DDS란 무엇인가? | ROS2 하루에 입문하기, accessed July 3, 2025, https://robertchoi.gitbook.io/ros2/03-dds |
| Navigating DDS: Basics, Limitations, and Integration with MQTT | by EMQ Technologies, accessed July 3, 2025, https://emqx.medium.com/navigating-dds-basics-limitations-and-integration-with-mqtt-944da6bde576 |
| Day 4: Designing a Distributed Messaging Queue: Kafka vs RabbitMQ | by Dev Cookies, accessed July 3, 2025, https://medium.com/@devcookies/day-4-designing-a-distributed-messaging-queue-kafka-vs-rabbitmq-c4abcc0767d3 |
| Bridging XMPP and DDS Messaging Frameworks | Object Computing, Inc., accessed July 3, 2025, https://objectcomputing.com/resources/publications/sett/april-2012-bridging-xmpp-and-dds-messaging-frameworks |
| Three Simple Steps to Achieving Peak DDS Performance | RTI - Real-Time Innovations, accessed July 3, 2025, https://www.rti.com/blog/three-simple-steps-to-achieving-peak-dds-performance/ |
| Useful tools to debug DDS issues | Data Distribution Service (DDS) Community RTI Connext Users, accessed July 3, 2025, https://community.rti.com/howto/useful-tools-debug-dds-issues |
| Top 6 message queues for distributed architectures | by IcePanel - Medium, accessed July 3, 2025, https://icepanel.medium.com/top-6-message-queues-for-distributed-architectures-a3cbabf08993 |