DDS 리스너(Listener) 아키텍처

DDS 리스너(Listener) 아키텍처

2025-12-13, G30DR

1. 서론: 분산 시스템에서의 데이터 중심 통신과 제어 역전

1.1 데이터 분산 서비스(DDS)의 패러다임 변화

현대의 국방, 항공우주, 자율주행 및 대규모 산업 자동화 시스템은 수천 개의 노드가 마이크로초(µs) 단위의 지연 시간 내에 데이터를 교환해야 하는 초고성능 분산 환경으로 진화하고 있다. 이러한 환경에서 시스템 통합의 복잡성을 줄이고 데이터 전달의 신뢰성을 보장하기 위해 OMG(Object Management Group)는 데이터 중심(Data-Centric)의 통신 미들웨어 표준인 DDS(Data Distribution Service)를 제정하였다.1

DDS는 DCPS(Data-Centric Publish-Subscribe) 모델을 기반으로 하며, 이는 데이터의 생성자(Publisher)와 소비자(Subscriber)를 시간적, 공간적으로 완전히 분리한다. 이러한 분리된 구조에서 애플리케이션이 데이터의 도착이나 네트워크 상태의 변화를 인지하는 방식은 시스템의 전체 아키텍처를 결정짓는 중요한 요소이다. 전통적인 클라이언트-서버 모델이나 단순한 메시지 큐 시스템과 달리, DDS는 QoS(Quality of Service) 정책에 기반하여 데이터의 유효성, 생존성, 신뢰성을 보장하며, 이에 따른 다양한 이벤트를 애플리케이션에 통지해야 한다.3

1.2 리스너(Listener)의 정의와 “쓰레드를 대신한다“는 개념

DDS 아키텍처에서 리스너(Listener)는 ’제어의 역전(Inversion of Control)’을 구현하는 핵심 메커니즘이다. 사용자 애플리케이션이 주도적으로 데이터를 읽기 위해 루프를 돌며 상태를 확인하는 것이 아니라, 데이터가 도착하거나 상태가 변경된 시점에 미들웨어가 애플리케이션 코드를 호출하는 구조를 취한다.

이러한 리스너의 특성은 DDS 도입 시 가장 큰 장점 중 하나인 “쓰레드 관리의 위임“과 직결된다. 개발자는 데이터 수신을 위해 별도의 폴링(Polling) 쓰레드를 생성하고, sleep() 주기나 select() 타임아웃을 조절하며 CPU 자원을 관리할 필요가 없다. 대신, 미들웨어 내부에서 최적화된 수신 쓰레드(Receive Thread)가 네트워크 I/O를 전담하고, 유의미한 이벤트가 발생했을 때만 사용자 로직을 실행시킨다.3 이는 개발자가 비즈니스 로직에만 집중할 수 있게 하여 코드의 간결성을 높이고, 데이터 도착 즉시 반응하는 이벤트 기반(Event-Driven) 처리를 가능하게 한다.

1.3 보고서의 목적 및 구성

본 보고서는 DDS 리스너 아키텍처의 이론적 배경부터 내부 동작 원리, 성능 특성, 그리고 실무 적용 시의 주의사항을 포괄적으로 분석한다. 특히 “미들웨어 쓰레드를 빌려 쓴다“는 특성이 가지는 양면성—지연 시간 최소화라는 장점과 데드락(Deadlock) 위험이라는 단점—을 심층적으로 파헤친다. 또한, RTI Connext, OpenDDS, Fast DDS 등 주요 DDS 구현체들의 쓰레딩 모델을 비교 분석하고, 고성능/고신뢰성 시스템 구축을 위한 권장 디자인 패턴인 ‘Producer-Consumer’ 모델과 ‘Worker Queue’ 전략을 상세히 기술한다.


2. DDS 통신 모델과 이벤트 통지 아키텍처

2.1 DCPS 엔티티 계층과 상태(Status) 관리

DDS의 통신은 DomainParticipant, Publisher, Subscriber, DataWriter, DataReader 등 계층화된 엔티티(Entity)들을 통해 이루어진다. 각 엔티티는 자신의 역할에 맞는 상태(Status) 정보를 실시간으로 관리한다. 예를 들어, DataReader는 데이터 도착(DATA_AVAILABLE), 매칭된 DataWriter의 발견(SUBSCRIPTION_MATCHED), 또는 데이터 수신 기한 초과(REQUESTED_DEADLINE_MISSED) 등의 상태를 가진다.1

이러한 상태 변화는 단순한 플래그 변경에 그치지 않고, 시스템의 건전성을 나타내는 중요한 지표가 된다. DDS 표준은 이러한 상태 변화를 애플리케이션에 전달하기 위해 두 가지 상반된 모델을 제공한다.

  1. WaitSet 모델: 애플리케이션이 주도권을 가지는 동기식(Synchronous) 모델이다. 사용자는 특정 조건(Condition)들이 충족될 때까지 자신의 쓰레드를 블로킹(wait())시킨다. 이는 전통적인 소켓 프로그래밍의 select 모델과 유사하다.6
  2. Listener 모델: 미들웨어가 주도권을 가지는 비동기식(Asynchronous) 모델이다. 사용자는 미리 정의된 인터페이스(Listener)를 구현하여 엔티티에 등록하고, 미들웨어는 상태 변경 시 해당 콜백 메서드를 호출한다.

2.2 리스너 인터페이스의 계층 구조 및 전파(Propagation)

리스너는 DDS 엔티티 계층 구조와 동일한 위계를 가진다. 모든 구체적인 리스너 인터페이스(예: DataReaderListener, TopicListener)는 최상위 Listener 인터페이스를 상속받는다. 이는 객체 지향적 설계 원칙을 따르며, 하위 엔티티에서 처리되지 않은 이벤트가 상위 엔티티로 전파되는 메커니즘을 지원한다.8

2.2.1 이벤트 전파 규칙

이벤트가 발생했을 때 미들웨어는 가장 구체적인(Specific) 리스너부터 탐색한다. 예를 들어, DataReader에 데이터가 도착했을 때의 처리 순서는 다음과 같다.

  1. DataReaderListener 확인: 해당 DataReader에 리스너가 등록되어 있고, DATA_AVAILABLE 상태가 마스크(StatusMask)로 활성화되어 있는지 확인한다. 존재한다면 on_data_available()을 호출하고 이벤트 처리를 종료한다.
  2. SubscriberListener 확인: DataReader에 리스너가 없다면, 상위 Subscriber의 리스너를 확인한다. SubscriberListener가 등록되어 있다면 on_data_on_readers()를 호출한다. 이는 여러 DataReader의 데이터를 한 곳에서 일괄 처리할 수 있는 기회를 제공한다.
  3. DomainParticipantListener 확인: 마지막으로 최상위 DomainParticipant의 리스너를 확인한다.

이러한 계층적 전파 방식은 개발자에게 설계의 유연성을 제공한다. 모든 데이터 처리를 중앙 집중화할 수도 있고, 개별 데이터 스트림별로 분산시킬 수도 있다.10

2.3 주요 리스너 콜백 함수와 트리거 조건

DDS 표준은 다양한 통신 상태에 대응하는 콜백 함수들을 정의하고 있다. 각 콜백은 특정 QoS 정책이나 네트워크 이벤트와 밀접하게 연관되어 있다.12

콜백 함수 명트리거 조건 및 의미관련 QoS / 상태
on_data_availableDataReader의 수신 큐에 새로운 데이터가 도착함. 가장 빈번하게 호출되며 데이터 처리의 핵심 진입점이다.DATA_AVAILABLE_STATUS
on_subscription_matched호환되는 QoS를 가진 DataWriter를 발견하거나 연결이 끊어짐. 시스템 토폴로지 변화를 감지한다.TOPIC, PARTITION, QoS Compatibility
on_liveliness_changed매칭된 DataWriter의 생존 상태(Alive/Not Alive)가 변경됨. LeaseDuration 내에 하트비트나 데이터가 오지 않을 때 발생한다.LIVELINESS
on_requested_deadline_missed설정된 주기(Deadline) 내에 데이터가 도착하지 않음. 실시간 시스템에서 타이밍 제약 위반을 경고한다.DEADLINE
on_sample_rejectedResourceLimits 초과 또는 History 깊이 제한 등으로 인해 수신된 샘플이 큐에 들어가지 못하고 버려짐.RESOURCE_LIMITS, HISTORY
on_sample_lost네트워크 패킷 손실이 복구되지 못하고 최종적으로 유실됨. 신뢰성 있는 통신(RELIABLE)에서도 발생할 수 있다.RELIABILITY
on_requested_incompatible_qosDataWriter를 발견했으나 QoS 설정이 호환되지 않아 연결할 수 없음.모든 QoS 정책

이 표에서 볼 수 있듯이 리스너는 단순한 데이터 수신뿐만 아니라 시스템의 연결 상태, 자원 상태, 실시간성 준수 여부 등을 포괄적으로 모니터링하는 수단이다.


3. 리스너의 내부 동작 원리와 쓰레드 모델

리스너가 “쓰레드를 대신한다“는 명제는 미들웨어 내부 아키텍처, 특히 수신 쓰레드(Receive Thread)의 동작 방식을 이해할 때 비로소 명확해진다.

3.1 미들웨어 수신 쓰레드(Receive Thread)의 역할

DDS 미들웨어는 초기화 시점에 전송 계층(Transport Layer)과의 인터페이스를 설정하고, 네트워크 포트(Port)를 감시하는 쓰레드를 생성한다. RTI Connext DDS의 경우, DomainParticipant 생성 시 내부적으로 데이터베이스 쓰레드, 이벤트 쓰레드와 함께 수신 쓰레드를 생성한다.15

수신 쓰레드는 다음과 같은 무한 루프를 수행한다.

  1. 대기 (Wait): 소켓 API(select, poll, epoll 등)를 사용하여 데이터 패킷이 도착할 때까지 대기한다. 이 상태에서는 CPU를 점유하지 않는다.
  2. 수신 (Receive): 데이터가 도착하면 즉시 깨어나 버퍼로부터 데이터를 읽어들인다.
  3. 역직렬화 (Deserialization): 네트워크 바이트 스트림(CDR 형식)을 메모리 상의 객체로 변환한다.
  4. 역다중화 (Demultiplexing): 패킷 헤더의 GUID(Global Unique Identifier)를 분석하여 어느 DataReader에게 전달될 데이터인지 판별한다.
  5. 저장 (Store): DataReaderHistoryCache에 데이터를 저장한다. 이때 ResourceLimitsHistory QoS를 확인하여 오래된 데이터를 덮어쓰거나 거부한다.
  6. 통지 (Notify): 해당 DataReader에 등록된 리스너가 있다면, 그 리스너의 on_data_available() 함수를 **직접 호출(Function Call)**한다.
  7. 복귀 (Loop): 리스너 함수가 리턴되면 다시 1번 단계로 돌아간다.

이 과정에서 주목해야 할 점은 6번 단계이다. 리스너 콜백은 별도의 쓰레드에서 실행되는 것이 아니라, 미들웨어의 수신 쓰레드 컨텍스트(Context)를 그대로 사용하여 실행된다. 이것이 바로 “쓰레드를 대신한다“는 말의 기술적 실체이다. 개발자가 만든 쓰레드는 없지만, 미들웨어의 쓰레드가 개발자의 코드를 실행해주는 것이다.16

3.2 벤더별 쓰레딩 모델 비교

DDS 표준은 동작(Behavior)을 정의할 뿐 구현(Implementation)을 강제하지 않으므로, 벤더마다 쓰레드 모델에 차이가 있다.

3.2.1 RTI Connext DDS

RTI Connext는 기본적으로 전송(Transport) 포트 단위로 수신 쓰레드를 생성한다. 기본 설정에서 UDPv4 유니캐스트 포트 하나당 하나의 수신 쓰레드가 할당된다. 중요한 점은 하나의 DomainParticipant 내에 있는 여러 DataReader들이 동일한 포트를 공유하는 경우(기본값), 이들은 모두 동일한 수신 쓰레드에 의해 구동된다는 것이다.18 따라서 하나의 DataReader 리스너에서 지연이 발생하면, 같은 포트를 공유하는 다른 모든 DataReader의 데이터 수신이 차단된다.

3.2.2 Fast DDS (구 Fast RTPS)

Fast DDS는 각 전송 디스크립터(Transport Descriptor)마다 수신 쓰레드를 생성한다. 또한 비동기 출판(Asynchronous Publication)을 위한 별도의 Async Writer Thread를 운영하여, 데이터 전송 시 애플리케이션 쓰레드의 블로킹을 최소화한다.19 Fast DDS의 아키텍처 문서에 따르면, 리스너 쓰레드는 들어오는 메시지를 FIFO 방식으로 처리하며, 여기서도 사용자 콜백의 실행 시간은 전체 파이프라인의 지연 시간에 직접적인 영향을 미친다.20

3.2.3 OpenDDS

OpenDDS는 ACE/TAO 프레임워크 기반의 Reactor 패턴을 사용한다. I/O 이벤트를 감지하는 Reactor 쓰레드가 존재하며, 이 쓰레드가 I/O 처리와 애플리케이션 콜백을 모두 담당할 수 있다. OpenDDS는 ThreadPerConnection이나 ThreadPool 등 다양한 전략을 설정할 수 있지만, 기본적으로 리스너 콜백은 미들웨어의 I/O 쓰레드에서 실행된다는 원칙은 동일하다.21

3.3 리스너 vs. WaitSet: 성능 및 아키텍처 비교

데이터 수신 방식의 선택은 시스템 성능에 지대한 영향을 미친다. 3과 4의 연구 결과에 따르면 다음과 같은 트레이드오프가 존재한다.

표 1. 리스너(Listener)와 웨이트셋(WaitSet) 비교

비교 항목리스너 (Listener)웨이트셋 (WaitSet)
패러다임Event-Driven (Push 방식)Polling / Blocking (Pull 방식)
실행 주체미들웨어 내부 수신 쓰레드사용자 애플리케이션 쓰레드
지연 시간 (Latency)최소화됨. 데이터 도착 즉시 함수 호출 (Context Switch 없음).중간. 쓰레드 깨움 및 스케줄링 오버헤드 발생 (Context Switch 발생).
처리량 (Throughput)데이터 빈도가 높으면 콜백 오버헤드로 인해 불리할 수 있음.데이터를 배치(Batch)로 처리하기 유리하여 고부하 상황에서 유리함.
구현 복잡도상대적으로 간단함. 비동기 로직 필요.별도 쓰레드 생성 및 루프 관리 필요.
안전성 (Safety)위험. 콜백 지연 시 전체 통신 마비 가능성.안전. 사용자 로직 지연이 미들웨어 코어에 영향 적음.
주요 용도알람, 상태 모니터링, 저지연 제어 명령, 간헐적 데이터.영상 스트리밍, 고빈도 센서 데이터, 복잡한 연산이 필요한 데이터.

리스너 방식은 쓰레드 컨텍스트 스위칭 비용을 제거하여 **극한의 저지연(Extreme Latency)**을 달성할 수 있다는 강력한 장점이 있다. 그러나 이는 “사용자 코드가 빨리 리턴해야 한다“는 엄격한 제약 조건을 전제로 한다.


4. 리스너 사용 시의 주의점과 동시성(Concurrency) 위험

DDS 리스너의 가장 큰 주의점은 “콜백 함수는 미들웨어의 내부 쓰레드를 빌려 쓰는 것“이라는 사실에서 기인한다. 이로 인해 발생할 수 있는 문제는 단순한 성능 저하를 넘어 시스템 전체의 데드락(Deadlock)으로 이어질 수 있다.

4.1 데드락(Deadlock) 발생 메커니즘

리스너 콜백 내부에서 DDS API를 다시 호출할 때 교착 상태가 발생할 수 있다. 이는 미들웨어가 내부 상태를 보호하기 위해 사용하는 락(Mutex)과 애플리케이션이 사용하는 락의 획득 순서가 꼬이기 때문이다.23

전형적인 AB-BA 데드락 시나리오:

  1. 미들웨어 쓰레드: 데이터 수신 → 내부 EntityLock(A) 획득 → 리스너의 on_data_available 호출 → 애플리케이션의 AppLock(B) 획득 시도 (대기).
  2. 애플리케이션 쓰레드: 비즈니스 로직 수행 중 AppLock(B) 획득 → DDS의 write() 또는 set_qos() 호출 → 내부 EntityLock(A) 획득 시도 (대기).

두 쓰레드가 서로 상대방이 가진 락을 기다리며 무한 대기 상태에 빠지게 된다. 이를 방지하기 위해 RTI와 같은 벤더는 “리스너 내부에서는 특정 API(예: write(), wait_for_acknowledgments(), 엔티티 삭제 등) 호출을 금지하거나 주의해야 한다“고 명시하고 있다.17 리스너 내에서는 오직 read(), take(), get_status_changes()와 같은 비차단(Non-blocking) 조회 함수만 사용하는 것이 안전하다.

4.2 블로킹(Blocking)과 시스템 전체 지연

리스너 내에서 파일 I/O, 데이터베이스 접속, 복잡한 수학 연산 등을 수행하여 실행 시간이 길어지면, 해당 수신 쓰레드는 소켓 버퍼에서 다음 데이터를 읽어오지 못한다.

  • UDP의 경우: OS의 소켓 수신 버퍼(Receive Buffer)가 가득 차게 되고, 이후 도착하는 패킷은 OS 커널에 의해 드롭(Drop)된다. 이는 DDS의 신뢰성 프로토콜(Reliability Protocol)을 트리거하여 재전송 요청(NACK) 폭주를 유발하고, 네트워크 혼잡을 가중시킨다.
  • TCP의 경우: TCP 윈도우 사이즈가 0이 되어 송신 측(Publisher)이 더 이상 데이터를 보내지 못하고 블로킹된다.

더욱 심각한 것은, 앞서 언급했듯이 하나의 수신 쓰레드가 여러 DataReader를 담당하는 경우이다. 중요도가 낮은 데이터의 리스너가 블로킹되면, 동일한 쓰레드를 사용하는 중요도가 높은 데이터(Critical Topic)까지 수신되지 않는 “Head-of-Line Blocking” 현상이 발생한다.17


5. 고성능/고안전성 시스템을 위한 리스너 구현 패턴

위와 같은 위험을 회피하면서 리스너의 저지연 이점을 활용하기 위해서는 정형화된 디자인 패턴을 적용해야 한다. 가장 널리 사용되고 권장되는 방식은 Producer-Consumer 패턴Worker Queue를 활용한 부하 분산(Offloading)이다.

5.1 Producer-Consumer 패턴과 Worker Queue

이 패턴의 핵심은 리스너(Producer)가 데이터를 “처리“하지 않고, 안전한 큐에 “전달“만 하고 즉시 리턴하는 것이다. 실제 처리는 별도의 워커 쓰레드(Consumer)가 담당한다.25

5.1.1 구현 아키텍처

  1. 공유 자원: 쓰레드 안전(Thread-Safe)한 큐(Queue). 락(Lock) 경합을 최소화하기 위해 ConcurrentQueueLock-Free Queue 사용을 권장한다.
  2. 리스너(Producer):
  • on_data_available 콜백 진입.
  • take()를 통해 미들웨어로부터 데이터를 가져옴.
  • 데이터를 복사(Deep Copy)하여 애플리케이션 객체 생성. (미들웨어 메모리인 LoanedSample은 리턴 시 반환되어야 하므로 복사 필수).
  • 큐에 객체 삽입(push).
  • 즉시 리턴.
  1. 워커 쓰레드(Consumer):
  • 루프를 돌며 큐를 감시(Polling 또는 Condition Variable 대기).
  • 큐에서 데이터를 꺼내(pop) 비즈니스 로직(DB 저장, 연산, UI 갱신 등) 수행.

이 방식은 미들웨어 쓰레드의 점유 시간을 ‘데이터 복사 + 큐 삽입’ 시간으로 일정하게 제한하여 네트워크 처리량을 보장한다.

5.2 Modern C++ (C++11/14) 리스너 구현 예제

현대적인 C++ API는 람다와 std::function을 활용하여 리스너 구현을 간소화한다. 아래 예제는 데이터를 복사하여 워커 쓰레드로 넘기는 패턴을 보여준다.27

C++

// Modern C++ 예제: 안전한 리스너 구현
class SafeListener : public dds::sub::NoOpDataReaderListener<MyType> {
ThreadSafeQueue<MyType>& work_queue_; // 외부 워커 큐 참조

public:
explicit SafeListener(ThreadSafeQueue<MyType>& queue) : work_queue_(queue) {}

void on_data_available(dds::sub::DataReader<MyType>& reader) override {
// 1. 데이터 접근 (Zero-Copy)
auto samples = reader.take();

// 2. 유효 데이터만 필터링하여 복사 후 큐에 삽입
for (const auto& sample : samples) {
if (sample.info().valid()) {
// LoanedSample은 큐에 직접 넣을 수 없으므로 복사본 생성
MyType data_copy = sample.data();
work_queue_.push(std::move(data_copy));
}
}
// 3. 즉시 리턴 (samples 소멸자가 호출되며 미들웨어에 메모리 반환)
}
};

5.3 Java 리스너 구현 예제

Java 환경에서는 가비지 컬렉션(GC)의 영향을 고려해야 한다. OpenDDS 등의 Java 바인딩은 JNI를 통해 호출되므로, 리스너 내부에서 과도한 객체 생성은 지양해야 한다.28

Java

// Java 예제: 리스너 구현
public class MyDataReaderListener extends DDS._DataReaderListenerLocalBase {
private final BlockingQueue<MyType> queue;

public MyDataReaderListener(BlockingQueue<MyType> queue) {
this.queue = queue;
}

@Override
public void on_data_available(DDS.DataReader reader) {
MyTypeDataReader myReader = MyTypeDataReaderHelper.narrow(reader);
MyTypeSeqHolder dataSeq = new MyTypeSeqHolder(new MyType);
SampleInfoSeqHolder infoSeq = new SampleInfoSeqHolder(new SampleInfo);

// 데이터 가져오기
int ret = myReader.take(dataSeq, infoSeq,...);

if (ret == RETCODE_OK.value) {
for (int i = 0; i < dataSeq.value.length; i++) {
if (infoSeq.value[i].valid_data) {
// 큐에 삽입 (블로킹되지 않는 offer 사용 권장)
queue.offer(dataSeq.value[i]);
}
}
// 미들웨어에 메모리 반환
myReader.return_loan(dataSeq, infoSeq);
}
}
}

6. QoS와 리스너의 상호작용 (Liveliness 및 Deadline)

리스너는 단순한 데이터 수신 외에도, QoS 정책에 의해 발생하는 시스템 이벤트를 처리하는 데 중요한 역할을 한다. 특히 LIVELINESSDEADLINE QoS는 리스너의 동작 방식을 이해하는 데 필수적이다.

6.1 Liveliness QoS와 on_liveliness_changed

Liveliness QoS는 시스템 내 엔티티들의 ’생존’을 확인하는 메커니즘이다.

  • AUTOMATIC: 미들웨어가 자동으로 참여자의 생존 신호(Heartbeat)를 보낸다.
  • MANUAL_BY_TOPIC: 애플리케이션이 명시적으로 write()하거나 assert_liveliness()를 호출해야 한다.30

만약 LeaseDuration 내에 신호가 오지 않으면, 미들웨어는 LIVELINESS_CHANGED 상태를 변경하고 DataReaderListeneron_liveliness_changed()를 호출한다. 이는 네트워크 단절이나 상대방 애플리케이션의 크래시(Crash)를 감지하는 가장 빠른 수단이 된다. 리스너 방식은 이러한 상태 변화를 즉각적으로 알 수 있어, Fail-over(장애 복구) 로직을 구현하기에 적합하다.

6.2 Deadline QoS와 on_requested_deadline_missed

실시간 시스템에서는 데이터가 ‘정해진 시간 내에’ 도착하는 것이 중요하다. Deadline QoS를 설정하면, 미들웨어는 데이터 도착 주기를 감시한다. 주기를 위반할 경우 on_requested_deadline_missed() 콜백이 트리거된다.

이 콜백은 시스템의 부하가 높아지거나 네트워크 지연이 발생하고 있음을 알리는 조기 경보 시스템(Early Warning System)으로 활용된다. 리스너를 통해 이 이벤트를 받으면, 애플리케이션은 데이터 전송 주기를 늦추거나(Flow Control), 사용자에게 경고를 표시하는 등의 대응을 할 수 있다.31


7. 결론

DDS의 리스너(Listener)는 “개발자가 쓰레드를 만들 필요 없이” 비동기 이벤트를 처리할 수 있게 해주는 강력한 추상화 도구이다. 이는 코드의 복잡성을 낮추고, 데이터 도착에 대한 즉각적인 반응성(Responsiveness)을 보장하여 이벤트 기반 아키텍처 구현을 용이하게 한다.

그러나 이러한 편의성은 미들웨어의 핵심 자원인 수신 쓰레드를 공유한다는 구조적 특성에서 비롯된다. 따라서 리스너 내부에서의 블로킹 작업이나 부적절한 락 사용은 전체 통신 시스템의 성능 저하와 데드락을 유발할 수 있는 잠재적 위험 요소이다.

성공적인 DDS 시스템 구축을 위해서는 다음과 같은 핵심 전략이 요구된다.

  1. 리스너의 경량화: 리스너 콜백은 데이터 복사 및 상태 플래그 설정 등 최소한의 작업만 수행하고 즉시 리턴해야 한다.
  2. 부하 분산 아키텍처: 실질적인 데이터 처리는 Worker Queue와 별도의 소비자 쓰레드를 통해 미들웨어 쓰레드와 분리해야 한다.
  3. 정교한 예외 처리: 리스너 내부의 예외 발생이 미들웨어 쓰레드를 중단시키지 않도록 철저한 예외 처리가 필요하다.
  4. QoS 활용: Liveliness 및 Deadline QoS 이벤트에 대한 리스너를 적극 활용하여 시스템의 건강 상태를 실시간으로 모니터링해야 한다.

결론적으로, 리스너는 DDS가 제공하는 고성능 통신의 입구(Entry Point)이며, 이 입구를 얼마나 효율적으로 관리하느냐가 전체 분산 시스템의 실시간 성능을 결정짓는 열쇠가 된다.

8. 참고 자료

  1. A Comparison and Mapping of Data Distribution Service and High …, https://info.rti.com/hubfs/whitepapers/Comparison_and_Mapping_of_DDS_and_HLA.pdf
  2. (PDF) OMG Data-Distribution Service: architectural overview, https://www.researchgate.net/publication/4070720_OMG_Data-Distribution_Service_architectural_overview
  3. NDDS Latency and Throughput - DDS Foundation, https://www.dds-foundation.org/sites/default/files/Evaluating_Performance_Publish_Subscribe_Platforms.pdf
  4. Use WaitSets, Except When You Need Extreme Latency, https://community.rti.com/best-practices/use-waitsets-except-when-you-need-extreme-latency
  5. Data Distribution Service for Real-Time Systems Specification, https://www.omg.org/spec/DDS/1.1/PDF/
  6. Waitsets | Data Distribution Service (DDS) Community RTI Connext …, https://community.rti.com/examples/waitsets
  7. Qt and RTI DDS interaction—Need some guidance - Stack Overflow, https://stackoverflow.com/questions/17834696/qt-and-rti-dds-interaction-need-some-guidance
  8. Conditions and Listeners - OpenDDS 3.33.0, https://opendds.readthedocs.io/en/latest-release/devguide/conditions_and_listeners.html
  9. Conditions and Listeners - OpenDDS 3.24.2, https://opendds.readthedocs.io/en/dds-3.24.2/devguide/conditions_and_listeners.html
  10. Should I combine my DataReaders under one Subscriber, or use …, https://community.rti.com/kb/should-i-combine-my-datareaders-under-one-subscriber-or-use-separate-subscribers
  11. RTI Connext Modern C++ API: Listener Class Reference, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp2/classListener.html
  12. RTI Connext Traditional C++ API: DDSDataWriterListener Class …, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp/classDDSDataWriterListener.html
  13. Listeners — Eclipse Cyclone DDS, 0.11.0, https://cyclonedds.io/docs/cyclonedds/latest/about_dds/listener.html
  14. 3.1.3. Status - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/dds_layer/core/status/status.html
  15. Identifying Threads Used by RTI Connext DDS Professional, https://community.rti.com/kb/identifying-threads-used-rti-connext-dds-professional
  16. Execution module - v3.0.0 - eProsima Safe DDS, https://safe-dds.docs.eprosima.com/main/architecture/execution.html
  17. Never Block in a Listener Callback - RTI Community, https://community.rti.com/best-practices/never-block-listener-callback
  18. 4.10. Threading Model — RTI Connext DDS Micro 2.4.14.0 …, https://community.rti.com/static/documentation/connext-micro/2.4.14/doc/html/usersmanual/threads.html
  19. Bounding the Data-Delivery Latency of DDS Messages in Real-Time …, https://drops.dagstuhl.de/storage/00lipics/lipics-vol262-ecrts2023/LIPIcs.ECRTS.2023.9/LIPIcs.ECRTS.2023.9.pdf
    1. Library Overview - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/library_overview/library_overview.html
  20. OpenDDS/java/FAQ at master - GitHub, https://github.com/objectcomputing/OpenDDS/blob/master/java/FAQ
  21. Reactive OpenDDS [Part I] - Object Computing, Inc., https://objectcomputing.com/resources/publications/mnb/2014/01/15/reactive-opendds-part-i
  22. How can I prevent deadlocks while invoking the RTI APIs from a …, https://community.rti.com/kb/how-can-i-prevent-deadlocks-while-invoking-rti-apis-listener
  23. Package: DDS.Listener - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_ada/dds-listener.ads.html
  24. The Producer Consumer Pattern - Jenkov.com, https://jenkov.com/tutorials/java-concurrency/producer-consumer.html
  25. Tutorial on How to Implement Producer-Consumer Pattern Using …, https://serengetitech.com/tech/tutorial-on-how-to-implement-producer-consumer-pattern-using-wait-and-notify-methods/
  26. RTI Connext Modern C++ API: DataReader Use Cases, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp2/group__DDSReaderExampleModule.html
  27. Java Bindings - OpenDDS 3.34.0-dev, https://opendds.readthedocs.io/en/master/devguide/java_bindings.html
  28. OpenDDS/java/tests/messenger/subscriber/DataReaderListenerImpl …, https://github.com/objectcomputing/OpenDDS/blob/master/java/tests/messenger/subscriber/DataReaderListenerImpl.java
  29. 59.15 LIVELINESS QosPolicy - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/LIVELINESS_QosPolicy.htm
  30. Quality of Service (QoS) policies are sets of requested … - OpenDDS, https://opendds.readthedocs.io/en/dds-3.28.1/devguide/quality_of_service.html