DataWriter와 Publisher의 생성 및 설정

DataWriter와 Publisher의 생성 및 설정

분산 시스템 미들웨어의 핵심인 DDS(Data Distribution Service)에서 데이터의 흐름을 시작하는 발원지는 바로 발행(Publication) 측 엔티티들이다. 이 장에서는 DDS의 DCPS(Data-Centric Publish-Subscribe) 계층 구조 내에서 데이터를 생성하고 네트워크로 송출하는 책임을 지는 PublisherDataWriter의 생성, 설정, 그리고 관리 메커니즘을 심층적으로 분석한다. 단순히 API를 호출하여 객체를 생성하는 절차적 설명을 넘어, OMG(Object Management Group)의 표준 명세가 의도하는 아키텍처적 설계 철학인 팩토리 패턴(Factory Pattern)의 적용 이유, 메모리 및 리소스 관리 전략, 그리고 QoS(Quality of Service) 정책이 엔티티 생성 시점에 어떻게 상호작용하여 통신 계약(Contract)을 형성하는지를 포괄적으로 다룬다.

DDS 애플리케이션의 성공적인 구현은 올바른 엔티티 생성에서 시작된다. DomainParticipant로부터 파생되는 Publisher, 그리고 Publisher로부터 파생되는 DataWriter로 이어지는 계층적 구조는 시스템의 논리적 파티셔닝(Partitioning), 리소스 격리, 그리고 일관된 QoS 적용을 가능하게 하는 토대이다. 본 절은 이러한 구조적 특성을 이해하고, 실제 C++ 및 현대적 언어 매핑 환경에서 견고한 발행 로직을 구현하기 위한 상세한 가이드라인을 제시한다.1

1. DDS 발행(Publication) 계층의 아키텍처와 팩토리 패턴

DDS 표준은 모든 엔티티의 생명주기를 관리하기 위해 엄격한 **팩토리 패턴(Factory Pattern)**을 채택하고 있다. 이는 객체의 생성과 소멸에 대한 책임을 클라이언트(애플리케이션)가 직접 지지 않고, 상위 관리 객체(Factory)에게 위임함으로써 리소스 관리의 안전성과 설정의 일관성을 보장하기 위함이다. 발행 측면에서 이 계층 구조는 DomainParticipant \rightarrow Publisher \rightarrow DataWriter의 순서로 구체화된다.1

1.1 DomainParticipant의 역할과 책임

모든 DDS 통신의 진입점인 DomainParticipant는 특정 도메인 ID(정수값)에 매핑되는 가상의 통신 컨테이너이다. 이 객체는 시스템 내부적으로 네트워크 트랜스포트(UDP, TCP, Shared Memory 등)를 초기화하고, 디스커버리(Discovery) 프로토콜을 수행하는 스레드를 관리한다.3 발행 측면에서 DomainParticipantPublisher 객체를 찍어내는 팩토리 역할을 수행한다.

중요한 점은 DomainParticipant가 단순히 객체 생성자 역할만 하는 것이 아니라, 하위 엔티티들이 공유할 수 있는 ’기본 설정(Default QoS)’의 저장소 역할을 한다는 것이다. 예를 들어, 애플리케이션 전체에서 사용될 Publisher들이 공통적으로 가져야 할 속성이 있다면, 이를 매번 설정하는 대신 DomainParticipant 레벨에서 set_default_publisher_qos 오퍼레이션을 통해 기본값을 변경함으로써, 이후 생성되는 모든 Publisher에 일관된 정책을 적용할 수 있다.5 이는 대규모 분산 시스템에서 설정의 파편화를 막고 유지보수성을 높이는 핵심 기제이다.

1.2 Publisher 엔티티의 아키텍처적 의미

많은 입문자가 Publisher 엔티티를 불필요한 중간 단계로 오해하곤 한다. DataWriter가 직접 데이터를 보내면 되는데 굳이 Publisher라는 컨테이너가 필요한 이유에 대해 의문을 가질 수 있다. 그러나 Publisher는 데이터 발행의 논리적 그룹을 정의하고, **데이터 간의 관계성(Coherency)과 순서(Ordering)**를 제어하는 결정적인 역할을 수행한다.2

Publisher는 다음과 같은 고유한 기능을 수행한다:

  1. 그룹화된 데이터 전송 제어: 여러 DataWriter가 하나의 Publisher에 속할 경우, 이들은 Publisher가 가진 PRESENTATION QoS 정책을 공유한다. 이를 통해 서로 다른 토픽의 데이터들이라도 논리적으로 연관되어 있다면, 수신 측에서 이들을 하나의 트랜잭션처럼 일관성 있게(Coherent) 처리하거나, 발생 순서대로(Ordered) 처리하도록 강제할 수 있다.7
  2. 공통 전송 정책 적용: PARTITION QoS를 Publisher 레벨에서 설정하면, 해당 Publisher에 속한 모든 DataWriter는 자동으로 해당 파티션(논리적 채널)으로 데이터가 격리된다. 이는 개별 Writer마다 설정을 반복할 필요를 없애준다.7
  3. 팩토리로서의 역할: PublisherDataWriter를 생성하는 팩토리이다. 이는 메모리 할당의 관점에서 볼 때, 연관된 DataWriter들이 Publisher가 관리하는 리소스 풀(Resource Pool)을 공유하거나, Publisher의 스레드 컨텍스트 내에서 동작하도록 최적화할 수 있는 여지를 제공한다.8

따라서 Publisher의 생성은 단순한 객체 생성이 아니라, 시스템 내 데이터 흐름의 ’파이프라인’을 건설하는 과정으로 이해해야 한다.

2. Publisher의 생성 및 설정 상세

Publisher의 생성은 DomainParticipant 객체의 create_publisher 오퍼레이션을 통해 수행된다. 이 과정에서 개발자는 PublisherQos, PublisherListener, 그리고 StatusMask를 인자로 전달해야 한다.

2.1 create_publisher 오퍼레이션의 시그니처와 매개변수

C++ (ISO C++ 2003 또는 Modern C++11 매핑) 환경에서 create_publisher의 전형적인 형태는 다음과 같다.10

DDS::Publisher* create_publisher(
const DDS::PublisherQos& qos,
DDS::PublisherListener* a_listener,
DDS::StatusMask mask
);
  • qos: 생성될 Publisher의 동작을 정의한다. DDS::PUBLISHER_QOS_DEFAULT 상수를 전달하면, DomainParticipant에 저장된 현재의 기본 QoS 설정을 사용한다.1
  • a_listener: 비동기 이벤트를 처리할 콜백 객체이다. NULL일 경우 리스너를 등록하지 않는다.
  • mask: 리스너가 활성화될 상태(Status)들의 비트마스크이다. DDS::STATUS_MASK_NONE 또는 DDS::STATUS_MASK_ALL 등을 사용할 수 있다.

이 함수는 성공 시 유효한 Publisher 객체의 포인터(또는 참조)를 반환하며, 실패 시(예: 리소스 부족, 호환되지 않는 QoS 등) NULL을 반환하거나 예외를 발생시킨다.9 따라서 견고한 시스템 구현을 위해서는 반환값에 대한 철저한 검증이 필수적이다.

2.2 EntityFactoryQosPolicy: 자동 활성화 제어

Publisher를 생성할 때 고려해야 할 가장 중요한 QoS 정책 중 하나는 EntityFactoryQosPolicy이다. 이 정책은 autoenable_created_entities라는 불리언 필드를 포함하고 있는데, 이는 팩토리 패턴의 깊이를 보여주는 설정이다.8

  • TRUE (기본값): create_publisher가 반환되는 즉시 Publisher는 활성화(Enabled) 상태가 된다. 즉, 생성과 동시에 내부 스레드가 동작하고 디스커버리 정보가 전파될 수 있다.14
  • FALSE: Publisher는 생성되지만 비활성화(Disabled) 상태로 유지된다. 개발자가 명시적으로 Publisher::enable()을 호출하기 전까지는 어떠한 통신 관련 작업도 수행하지 않는다.15

이 기능이 필요한 시나리오는 ’초기화의 원자성(Atomicity)’을 보장해야 할 때이다. 예를 들어, Publisher를 생성한 후, 트랜스포트 관련 설정을 미세 조정하거나, 관련된 DataWriter들을 모두 생성하고 이들의 리스너를 완벽히 셋업한 후에 일제히 통신을 시작해야 하는 경우가 있다. 이때 autoenable_created_entitiesFALSE로 설정하면 불완전한 상태에서 데이터가 노출되거나 불필요한 디스커버리 트래픽이 발생하는 것을 방지할 수 있다.

또한, DomainParticipant 생성 시 EntityFactory QoS를 FALSE로 설정했다면, create_publisher를 통해 생성된 Publisher 역시 자동으로 비활성화 상태가 된다는 점에 유의해야 한다. 계층 구조상 상위 엔티티가 비활성화되어 있다면, 하위 엔티티를 활성화하려 시도해도 실패하거나 상위 엔티티가 활성화될 때까지 지연된다.13

2.3 Publisher QoS 정책 심층 분석

Publisher 레벨에서 설정 가능한 주요 QoS 정책들은 데이터의 그룹 관리와 관련이 깊다. 다음의 표는 PublisherQos 구조체에 포함된 핵심 정책들을 요약한 것이다.7

QoS 정책구성 필드기본값의미 및 영향변경 가능 여부
PRESENTATIONaccess_scope coherent_access ordered_accessINSTANCE FALSE FALSE데이터 인스턴스 간의 연관성 및 순서 보장 범위 설정. GROUP으로 설정 시 해당 Publisher 내의 모든 Writer에 대해 원자적 읽기/쓰기 지원.Yes
PARTITIONname (String Sequence)Empty논리적 채널 분리. 동일한 토픽이라도 파티션 문자열이 일치하는 Reader/Writer끼리만 통신함.Yes
GROUP_DATAvalue (Octet Sequence)Empty애플리케이션 정의 메타데이터. 디스커버리 과정에서 Subscriber에게 전달됨. 보안 토큰이나 애플리케이션 ID 전달에 유용.Yes
ENTITY_FACTORYautoenable_created_entitiesTRUE하위 엔티티(DataWriter) 생성 시 자동 활성화 여부 결정.Yes
ASYNCHRONOUS_PUBLISHER (확장)disable_asynchronous_write thread_settingsFALSE …비동기 발행 모드 활성화. 큰 데이터를 보내거나 느린 컨슈머가 있을 때 별도의 스레드를 사용하여 송신을 분리함.No
  1. PRESENTATION QoS:

이 정책은 Publisher의 존재 의의를 가장 잘 나타낸다. access_scope를 GROUP으로 설정하고 coherent_access를 TRUE로 설정하면, 수신 측(Subscriber)은 해당 Publisher에 속한 여러 DataWriter들이 보낸 데이터 세트가 모두 도착할 때까지 애플리케이션에 데이터를 노출하지 않는다. 이는 마치 데이터베이스의 트랜잭션 커밋(Commit)과 유사한 개념으로, 분산 시스템에서 여러 토픽 간의 데이터 정합성을 보장해야 할 때 필수적이다.7

  1. PARTITION QoS:

물리적으로 같은 네트워크, 같은 도메인을 사용하더라도 논리적으로 통신 그룹을 분리하고 싶을 때 사용한다. 예를 들어, ’TopicA’를 사용하는 ‘제어실(ControlRoom)’ 파티션과 ‘로그(Log)’ 파티션을 나누어, 제어 명령이 로그 시스템으로 잘못 전달되어 실행되는 것을 방지할 수 있다. 파티션 이름은 와일드카드(*, ?)를 지원하여 유연한 매칭이 가능하다.7

  1. GROUP_DATA QoS:

이 정책은 DDS 미들웨어 자체의 동작에는 영향을 주지 않지만, 디스커버리 과정에서 Publisher에 대한 부가 정보를 전파하는 데 사용된다. Subscriber 측에서 Discovery 리스너를 통해 이 값을 확인하고, 특정 Publisher와의 매칭을 승인하거나 거부하는 로직을 구현할 때 활용할 수 있다.17

3. 기본 QoS 설정의 상속과 변경

DomainParticipant::set_default_publisher_qos 메서드는 팩토리 패턴의 유연성을 극대화한다. 애플리케이션 초기화 단계에서 이 메서드를 호출하여 기본 QoS를 설정해 두면, 이후 코드 곳곳에서 create_publisher(PUBLISHER_QOS_DEFAULT,...)를 호출할 때마다 일관된 설정이 적용된다.

이 메서드는 ‘대체(Replace)’ 방식이 아닌 ‘병합(Merge)’ 방식이 아님에 주의해야 한다. 즉, set_default_publisher_qos를 호출할 때 전달한 QoS 구조체로 기본값이 완전히 덮어씌워진다. 따라서 특정 필드만 변경하고 싶다면, 먼저 get_default_publisher_qos로 현재 설정을 가져온 뒤, 필요한 필드만 수정하여 다시 set 하는 패턴을 사용해야 한다.5

// C++ 예시: 기본 Publisher QoS의 Partition만 변경하기
DDS::PublisherQos pub_qos;
participant->get_default_publisher_qos(pub_qos); // 현재 기본값 가져오기

// 파티션 이름 설정
pub_qos.partition.name.length(1);
pub_qos.partition.name = DDS::string_dup("SensorData");

// 변경된 QoS를 다시 기본값으로 설정
participant->set_default_publisher_qos(pub_qos);

// 이후 생성되는 Publisher는 "SensorData" 파티션을 가짐
DDS::Publisher* pub = participant->create_publisher(
DDS::PUBLISHER_QOS_DEFAULT, NULL, DDS::STATUS_MASK_NONE);

이러한 패턴은 코드의 중복을 줄이고 설정의 중앙 집중화를 가능하게 한다.

4. 데이터 타입 시스템과 등록 (Type Registration)

Publisher가 준비되었다면 다음 단계는 DataWriter를 생성하는 것이지만, 그전에 반드시 선행되어야 할 작업이 있다. 바로 **데이터 타입의 등록(Type Registration)**이다. DDS는 데이터 중심(Data-Centric) 미들웨어이므로, 시스템은 전송되는 데이터의 구조(Schema)를 명확히 인지하고 있어야 한다. 이는 단순한 바이트 배열을 전송하는 소켓 통신과 DDS를 구분 짓는 가장 큰 특징 중 하나이다.4

4.1 IDL (Interface Definition Language)과 TypeSupport

DDS 개발의 표준 워크플로우는 OMG IDL을 사용하여 데이터 구조를 정의하는 것에서 시작한다.

// MyData.idl
struct SensorInfo {
long id;
string location;
double value;
};
#pragma keylist SensorInfo id // id를 키(Key)로 지정 (구현체마다 문법 상이)

이 IDL 파일은 DDS 벤더가 제공하는 컴파일러(예: RTI의 rtiddsgen, OpenDDS의 opendds_idl, FastDDS의 fastddsgen)를 통해 타겟 프로그래밍 언어의 소스 코드로 변환된다. 이 과정에서 생성되는 핵심 클래스가 바로 TypeSupport (예: SensorInfoTypeSupport)이다.9

TypeSupport 클래스는 다음과 같은 기능을 캡슐화한다:

  • 직렬화/역직렬화(CDR Marshaling): 언어별 데이터 객체를 네트워크 전송용 바이트 스트림(Common Data Representation)으로 변환.
  • 메타데이터(TypeCode): 데이터 구조의 이름, 필드 타입, 크기 등의 정보.
  • 키(Key) 관리: 데이터 인스턴스를 식별하는 키 필드의 해시 계산 및 비교 로직.19

4.2 register_type 오퍼레이션의 메커니즘

DataWriter를 생성하려면 먼저 Topic이 있어야 하고, Topic을 생성하려면 해당 Topic이 사용할 데이터 타입이 DomainParticipant에 등록되어 있어야 한다.

// C++ 예시
SensorInfoTypeSupport_var ts = new SensorInfoTypeSupportImpl();
if (ts->register_type(participant, "MySensorType")!= DDS::RETCODE_OK) {
// 오류 처리: 등록 실패
}

register_type 메서드는 두 개의 인자를 받는다. 첫 번째는 타입을 등록할 DomainParticipant, 두 번째는 해당 타입을 식별할 **등록 이름(Registered Name)**이다. 이 등록 이름은 매우 중요한데, 추후 create_topic을 호출할 때 이 이름을 참조하여 토픽과 데이터 타입을 연결하기 때문이다.10

일반적으로 TypeSupport 클래스는 get_type_name()과 같은 헬퍼 메서드를 제공하여 IDL에 정의된 기본 이름(예: “SensorInfo”)을 반환하지만, 개발자가 원한다면 별칭(Alias)을 사용하여 등록할 수도 있다. 이를 통해 동일한 물리적 데이터 구조체라도 논리적으로 다른 타입 이름으로 취급하여 시스템을 구성하는 것이 가능하다.

4.3 동적 타입 (Dynamic Types / XTypes)

최신 DDS 표준(XTypes)은 컴파일 타임에 IDL이 없어도 런타임에 데이터 타입을 정의하고 등록할 수 있는 DynamicType 기능을 제공한다.21

// 동적 타입 생성 및 등록 예시 (개념적)
DynamicTypeBuilderFactory_var factory = DynamicTypeBuilderFactory::get_instance();
DynamicTypeBuilder_var builder = factory->create_struct_builder("DynamicSensor");
builder->add_member(0, "id", factory->get_primitive_type(TK_LONG));
//... 멤버 추가...
DynamicType_var dyn_type = builder->build();
TypeSupport dyn_ts = new DynamicTypeSupport(dyn_type);
dyn_ts->register_type(participant, "DynamicSensorType");

이 방식은 반영형(Reflective) 시스템이나 범용 레코더/모니터링 도구를 개발할 때 유용하다. 하지만 일반적인 애플리케이션 개발에서는 컴파일 타임의 정적 타입 체크와 성능 최적화(인라인 함수 등) 이점을 누릴 수 있는 IDL 기반의 정적 타입 방식을 권장한다.

5. DataWriter의 생성과 Type Narrowing

타입 등록과 토픽 생성이 완료되었다면, 이제 Publisher를 통해 DataWriter를 생성할 수 있다. DataWriter는 DDS 통신의 실무자로서, 실제 데이터를 캐시에 쓰고 네트워크로 밀어 넣는 역할을 수행한다.

5.1 create_datawriter 오퍼레이션

DDS::DataWriter* create_datawriter(
DDS::Topic* a_topic,
const DDS::DataWriterQos& qos,
DDS::DataWriterListener* a_listener,
DDS::StatusMask mask
);

이 함수는 Publisher 생성과 유사한 패턴을 따르지만, 첫 번째 인자로 Topic 객체를 요구한다는 점이 다르다. 이 TopicDataWriter가 데이터를 기입할 대상을 지정한다. 여기서 중요한 제약 조건은, create_datawriter를 호출하는 Publisher와 인자로 전달된 Topic이 반드시 **동일한 DomainParticipant**로부터 생성된 것이어야 한다는 점이다. 서로 다른 도메인에 속한 엔티티끼리는 엮일 수 없다.10

qos 인자에는 DDS::DATAWRITER_QOS_DEFAULT를 사용하여 Publisher가 가지고 있는 기본 Writer 설정을 적용할 수 있다. 또한, Publishercopy_from_topic_qos 메서드를 사용하면, Topic에 설정된 QoS 값을 기반으로 DataWriter의 QoS를 초기화한 뒤 필요한 부분만 수정(Override)하여 생성하는 것도 가능하다.1 이는 데이터 중심 설계에서 데이터 자체(Topic)의 속성이 전송 방식(DataWriter)을 결정하도록 하는 자연스러운 흐름을 지원한다.

5.2 Type Safety와 Narrowing (형변환)

C++와 같은 정적 타입 언어에서 DDS 프로그래밍의 가장 큰 특징 중 하나는 Narrowing(좁히기) 과정이다. create_datawriter 메서드는 IDL로 정의된 구체적인 타입(예: SensorInfo)을 알지 못하는 Publisher 클래스에 정의되어 있으므로, 반환 타입은 범용 인터페이스인 DDS::DataWriter*이다.

그러나 애플리케이션은 SensorInfo 구조체를 인자로 받는 write() 메서드가 필요하다. DDS::DataWriter 클래스에는 void write(void* data) 같은 메서드가 존재하지 않거나, 존재하더라도 타입 안전성을 보장하지 않는다. 따라서 반환된 포인터를 구체적인 SensorInfoDataWriter*로 변환해야 한다.

이때 C++의 dynamic_cast 대신 DDS 표준이 제공하는 narrow() 정적 메서드를 사용해야 한다.23

DDS::DataWriter_var generic_writer = pub->create_datawriter(topic,...);
SensorInfoDataWriter_var sensor_writer = SensorInfoDataWriter::narrow(generic_writer);

if (sensor_writer == NULL) {
// 치명적 오류: 생성된 Writer가 SensorInfo 타입과 호환되지 않음
}

narrow() 메서드는 다음과 같은 중요한 역할을 수행한다:

  1. 타입 안전성 검증: 해당 객체가 실제로 요청한 타입(SensorInfo)을 처리하는 Writer인지 런타임에 확인한다. 만약 create_datawriter에 다른 타입의 토픽을 넣었다면 narrowNULL을 반환하여 잘못된 메모리 접근을 원천 차단한다.25
  2. 구현체 매핑: DDS 벤더의 내부 구현 클래스로 포인터를 안전하게 변환하여, 최적화된 write 메서드에 접근할 수 있게 해준다.

이 과정은 Java나 C# 같은 언어에서는 제네릭(Generics)이나 일반적인 캐스팅으로 처리되지만, C++에서는 DDS의 타입 안전성 철학을 구현하는 핵심 패턴이므로 반드시 숙지해야 한다.23

6. DataWriter QoS 정책과 호환성 (RxO 모델)

DataWriter의 생성 설정에서 가장 복잡하고도 중요한 부분은 QoS 정책의 구성이다. DataWriter의 QoS는 단순히 데이터를 어떻게 보낼지를 정하는 것을 넘어, 수신 측인 DataReader와의 연결 가능 여부를 결정하는 **계약(Contract)**으로 작용한다. 이를 DDS에서는 RxO (Requested vs. Offered) 모델이라고 부른다.26

6.1 RxO (Request / Offered) 호환성 원칙

RxO 모델의 대원칙은 **“제공자(Writer)는 요청자(Reader)가 요구하는 수준 이상의 품질을 제공해야 한다”**는 것이다. 만약 DataWriter가 제공하는 서비스 품질(Offered QoS)이 DataReader가 요청하는 품질(Requested QoS)보다 낮다면, DDS 미들웨어는 두 엔티티의 매칭을 거부하고 통신을 연결하지 않는다. 이때 양측에는 INCOMPATIBLE_QOS 상태 이벤트가 발생한다.28

주요 RxO QoS 정책과 호환성 규칙은 다음과 같다.

QoS 정책DataWriter (Offered)DataReader (Requested)호환성 규칙 및 설명
RELIABILITYkind: BEST_EFFORT or RELIABLEkind: BEST_EFFORT or RELIABLEOffered \ge Requested Writer가 Reliable이면 Reader는 Reliable/BestEffort 모두 가능. Writer가 BestEffort이면 Reader는 반드시 BestEffort여야 함.
DURABILITYkind: VOLATILE, TRANSIENT_LOCAL,…kind: VOLATILE, TRANSIENT_LOCAL,…Offered \ge Requested Writer가 데이터를 저장하지 않는데(Volatile), Reader가 과거 데이터를 요청(Transient Local)하면 불일치.
DEADLINEperiod: 시간 간격period: 시간 간격Offered \le Requested Writer는 Reader가 요구하는 주기보다 더 짧거나 같은 주기로 데이터를 갱신하겠다고 약속해야 함.
LATENCY_BUDGETduration: 시간duration: 시간Offered \le Requested Writer는 Reader가 허용하는 지연 시간보다 더 빨리 처리할 수 있어야 함.
OWNERSHIPkind: SHARED or EXCLUSIVEkind: SHARED or EXCLUSIVE일치(Match) 필수 양측의 소유권 모델이 정확히 같아야 함. 혼용 불가.
DESTINATION_ORDERkind: BY_RECEPTION or BY_SOURCEkind: BY_RECEPTION or BY_SOURCEOffered \ge Requested 소스 타임스탬프 기준 정렬이 더 엄격한(상위) 기준임.

[표 5.1] 주요 RxO QoS 정책의 호환성 매트릭스 30

이러한 규칙은 시스템 설계 시 매우 중요한 함의를 갖는다. 예를 들어, 처음에 BEST_EFFORT로 설계된 시스템을 나중에 RELIABLE로 업그레이드할 때, Publisher 측을 먼저 RELIABLE로 변경하면 기존의 BEST_EFFORT Subscriber들과도 호환성이 유지된다. 하지만 Subscriber를 먼저 RELIABLE로 변경하면, 아직 업그레이드되지 않은 BEST_EFFORT Publisher들과의 통신이 끊어지게 된다. 따라서 배포 전략 수립 시 RxO 규칙을 고려한 순서 결정이 필수적이다.

6.2 DataWriter 전용 QoS 및 리소스 관리

호환성 체크 외에도 DataWriter의 내부 동작을 제어하는 중요한 QoS들이 있다. 이는 DataWriter 생성 시 미들웨어가 내부적으로 메모리를 어떻게 할당하고 관리할지를 결정한다.8

  1. HISTORY (KEEP_LAST vs KEEP_ALL):
  • KEEP_LAST (depth=N): 각 인스턴스(Key) 별로 최근 N개의 샘플만 유지한다. 오래된 데이터는 덮어씌워진다. 텔레메트리나 센서값 갱신에 적합하다.
  • KEEP_ALL: 리소스 한계에 도달할 때까지 모든 데이터를 보관한다. 이벤트 로그나 금융 트랜잭션 등 유실이 없어야 하는 데이터에 적합하다.
  1. RESOURCE_LIMITS:
  • max_samples, max_instances, max_samples_per_instance: DataWriter가 관리할 수 있는 메모리 총량을 제한한다.
  • DDS 구현체들은 이 값을 기반으로 DataWriter 생성 시 **메모리 풀(Memory Pool)을 사전 할당(Pre-allocation)**하는 경우가 많다. 이는 런타임 성능을 극대화하고 메모리 파편화를 방지하기 위함이다. 따라서 RESOURCE_LIMITSUNLIMITED로 설정할 경우, 동적 할당으로 인한 성능 저하가 발생할 수 있음을 인지해야 한다.8
  1. WRITER_DATA_LIFECYCLE:
  • autodispose_unregistered_instances: DataWriter가 특정 인스턴스의 등록을 해제(Unregister)했을 때, 자동으로 그 인스턴스를 폐기(Dispose) 상태로 만들지를 결정한다. 이는 데이터의 생명주기 관리 자동화에 유용하다.

7. 리스너(Listener)와 상태(Status) 관리

DataWriter 생성의 마지막 인자인 ListenerStatusMask는 애플리케이션이 미들웨어의 상태 변화를 감지하고 반응하는 메커니즘을 제공한다.

DDS는 폴링(Polling) 방식의 Condition과 이벤트 구동(Event-Driven) 방식의 Listener를 모두 지원하지만, 실시간성이 중요한 시스템에서는 즉각적인 반응을 위해 리스너가 자주 사용된다. StatusMask는 리스너가 호출될 이벤트의 종류를 필터링하는 비트마스크이다.31

7.1 주요 DataWriter Status

  • LIVELINESS_LOST: LivelinessQosPolicy에 설정된 lease_duration 내에 DataWriter가 생존 신호(데이터 쓰기 또는 assert_liveliness 호출)를 보내지 못했음을 알린다. 이는 애플리케이션 로직이 멈추었거나 스레드가 데드락에 빠졌음을 감지하는 워치독(Watchdog)으로 활용된다.13
  • OFFERED_DEADLINE_MISSED: DeadlineQosPolicy에 설정된 주기 내에 새로운 데이터를 쓰지 못했음을 알린다. 주기적인 데이터 전송이 필수적인 시스템(예: 심박수 모니터링)에서 유용하다.30
  • OFFERED_INCOMPATIBLE_QOS: 앞서 설명한 RxO 불일치가 발생했을 때 호출된다. 개발/통합 단계에서 설정 오류를 잡는 데 결정적인 역할을 한다. OfferedIncompatibleQosStatus 구조체를 통해 어떤 정책이 문제였는지, 몇 번이나 발생했는지 확인할 수 있다.28
  • PUBLICATION_MATCHED: 호환되는 DataReader를 발견하여 연결되었거나, 기존 연결이 끊어졌을 때 호출된다. 현재 몇 개의 Reader와 연결되어 있는지(current_count) 정보를 제공하므로, “모든 구독자가 준비될 때까지 대기“하는 로직을 구현할 때 사용된다.33

7.2 리스너 구현 시 주의사항 (Threading Model)

리스너의 콜백 함수(예: on_publication_matched)는 애플리케이션 스레드가 아닌, DDS 미들웨어의 내부 수신 스레드 컨텍스트에서 호출된다. 이는 매우 중요한 의미를 갖는다.3

  1. 블로킹 금지: 리스너 내부에서 긴 시간이 걸리는 작업이나 블로킹 I/O를 수행하면, 미들웨어의 수신 스레드가 멈추게 되어 전체 통신 마비(Heartbeat 누락 등)를 초래할 수 있다. 무거운 작업은 별도 스레드로 위임해야 한다.
  2. 교착 상태(Deadlock) 회피: 리스너 내부에서 다시 DDS API를 호출할 때(예: write 호출), 미들웨어 내부 락(Lock)과 충돌하여 데드락이 발생할 위험이 있다. DDS 구현체마다 리스너 내에서 허용되는 API의 범위가 다르므로 문서를 확인해야 한다. 일반적으로 해당 엔티티의 상태를 조회하거나(get_status), 다른 엔티티를 조작하는 것은 안전하지만, 리스너를 발생시킨 엔티티를 삭제하는 등의 행위는 위험하다.

8. 디스커버리와 초기 통신 흐름

create_datawriter가 성공적으로 반환되고 활성화(enable)되면, DDS 미들웨어는 내부적으로 디스커버리(Discovery) 프로세스를 시작한다. 이는 DataWriter의 존재와 QoS 정보를 담은 DATA(w) 메시지를 네트워크(일반적으로 멀티캐스트)에 전파하는 과정이다.4

이 정보는 네트워크상의 모든 DomainParticipant에게 전달되며, 각 Participant는 자신이 보유한 DataReader들의 정보와 비교하여 매칭 여부를 판단한다. 매칭이 성사되면 PUBLICATION_MATCHED 상태가 갱신되고, DataWriter는 비로소 write() 호출 시 데이터를 전송할 준비를 마친다. DurabilityTRANSIENT_LOCAL 이상인 경우, 이 시점에 이미 캐시에 저장된 과거 데이터(Historical Data)가 있다면 신규 연결된 Reader에게 자동으로 전송(Push)된다.30

9. 요약 및 구현 권장사항

DataWriterPublisher의 생성 및 설정은 DDS 시스템의 성능과 안정성을 결정짓는 기초 공사이다. 다음은 본 절에서 논의한 내용을 바탕으로 한 구현 권장사항이다.

  1. QoS 프로파일 활용: 코드 내에 QoS 값을 하드코딩하기보다, XML 기반의 QoS 프로파일 기능을 사용하여 PUBLISHER_QOS_DEFAULT가 XML 설정을 참조하도록 구성하라. 이는 재컴파일 없이 시스템 동작을 튜닝할 수 있게 해준다.4
  2. Narrowing 필수: create_datawriter 반환값은 반드시 Type::narrow()를 통해 형변환하고, NULL 체크를 수행하여 타입 안전성을 확보하라.23
  3. RxO 규칙 준수: 시스템 아키텍처 설계 시 데이터 흐름의 방향뿐만 아니라, QoS의 상하위 호환성을 고려하여 배포 순서와 설정값을 정의하라.29
  4. Listener의 경량화: 리스너 콜백은 최대한 가볍게 유지하고, 상태 변경 플래그만 세팅하거나 이벤트를 큐에 넣는 방식으로 구현하여 미들웨어 스레드의 부하를 최소화하라.
  5. 리소스 제한 설정: RESOURCE_LIMITS QoS를 명시적으로 설정하여 메모리 사용량의 상한선을 긋고, 예상치 못한 메모리 폭증을 방지하라.8

이러한 원칙들을 준수함으로써 개발자는 DDS가 제공하는 데이터 중심 통신의 강력한 기능(시간 제약, 내구성, 신뢰성 등)을 십분 활용하는 견고한 발행 애플리케이션을 구축할 수 있다.

10. 참고 자료

  1. Data Distribution Service (DDS) - Object Management Group (OMG), https://www.omg.org/spec/DDS/1.4/PDF
  2. Introduction to DDS - OpenDDS 3.33.0, https://opendds.readthedocs.io/en/latest-release/devguide/introduction_to_dds.html
  3. OMG Data-Distribution Service (DDS) - DTIC, https://apps.dtic.mil/sti/tr/pdf/ADA433157.pdf
  4. Introduction to OpenDDS, https://opendds.org/about/articles/Article-Intro.html
  5. CoreDX Data Distribution Service: DDS::DomainParticipant Class Reference, https://www.twinoakscomputing.com/documents/refman_html_3.6.8/CoreDX_DDS_CPP_Reference_3.6.8/classDDS_1_1DomainParticipant.html
  6. DDS::DomainParticipant Class Reference - Twin Oaks Computing, Inc, https://www.twinoakscomputing.com/documents/refman_html_4.0.10/CoreDX_DDS_CPP_Reference_4.0.10/classDDS_1_1DomainParticipant.html
  7. Setting Publisher QosPolicies - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/Setting_Publisher_QosPolicies.htm
  8. Data Distribution Service for Real-Time Systems Specification - Object Management Group (OMG), https://www.omg.org/spec/DDS/1.1/PDF/
  9. Getting Started - OpenDDS 3.34.0-dev, https://opendds.readthedocs.io/en/latest/devguide/getting_started.html
  10. CoreDX DDS Hello World Example in C++ - Twin Oaks Computing, Inc, https://www.twinoakscomputing.com/coredx/examples/hello_cpp
  11. OpenDDS/examples/DCPS/IntroductionToOpenDDS/publisher.cpp at master - GitHub, https://github.com/objectcomputing/OpenDDS/blob/master/examples/DCPS/IntroductionToOpenDDS/publisher.cpp
  12. RTI Connext Traditional C++ API: HelloWorld_publisher.cxx - RTI Community, https://community.rti.com/static/documentation/connext-dds/7.4.0/doc/api/connext_dds/api_cpp/HelloWorld_publisher_8cxx-example.html
  13. Entities — Eclipse Cyclone DDS, 0.11.0, https://cyclonedds.io/docs/cyclonedds/latest/api/entity.html
  14. 20.1.1.7.12. EntityFactoryQosPolicy - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/api_reference/dds_pim/core/policy/entityfactoryqospolicy.html
  15. ENTITY_FACTORY QoS Parameter [DDS Foundation Wiki], https://www.omgwiki.org/ddsf/doku.php?id=ddsf:public:guidebook:06_append:02_quality_of_service:entity_factory
  16. QoS Policy List - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/qos_reference/qos_reference/qos_guide_all_in_one.htm
  17. Quality of Service - OpenDDS 3.25.0, https://opendds.readthedocs.io/en/dds-3.25/devguide/quality_of_service.html
  18. 1.3. Writing a simple C++ publisher and subscriber application - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/getting_started/simple_app/simple_app.html
  19. Advanced Tutorial - DDS Foundation, https://www.dds-foundation.org/sites/default/files/DDS_Advanced_Tutorial_2006_00-T1-2_Pardo.pdf
  20. 3.5.6. Definition of data types - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.x/fastdds/dds_layer/topic/typeSupport/typeSupport.html
    1. Configuring Fast DDS DynamicTypes - eProsima DDS Record & Replay - Read the Docs, https://dds-recorder.readthedocs.io/en/v0.3.0/rst/tutorials/dynamic_types.html
  21. RTI Connext Traditional C++ API: DDSDynamicDataTypeSupport Class Reference, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp/classDDSDynamicDataTypeSupport.html
  22. What is the purpose of the narrow() method created for a type by rtiddsgen? | Data Distribution Service (DDS) Community RTI Connext Users, https://community.rti.com/kb/what-purpose-narrow-method-created-type-rtiddsgen
  23. Chapter 34 DataWriters - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/DataWriters.htm
  24. A problem with narrow function | Data Distribution Service (DDS) Community RTI Connext Users, https://community.rti.com/content/forum-topic/problem-narrow-function-0
  25. Setting DataWriter QosPolicies - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/Setting_DataWriter_QosPolicies.htm
    1. Basic QoS — RTI Connext Getting Started documentation - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/getting_started_guide/cpp11/intro_qos.html
  26. RTI Connext .NET API (legacy): DDS::RequestedIncompatibleQosStatus Class Reference, https://community.rti.com/static/documentation/connext-dds/6.1.2/doc/api/connext_dds/api_dotnet/classDDS_1_1RequestedIncompatibleQosStatus.html
  27. Advanced Tutorial Using QoS to Solve Real-World Problems - DDS Foundation, https://www.dds-foundation.org/sites/default/files/DDS_Advanced_Ttutorial_00-T5_Hunt-revised.pdf
  28. 3.1.2.1. Standard QoS Policies - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.4.x/fastdds/dds_layer/core/policy/standardQosPolicies.html
  29. RTI Connext Modern C++ API: dds::core::status::StatusMask Class Reference, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_cpp2/classdds_1_1core_1_1status_1_1StatusMask.html
  30. RTI Connext C API: Status Kinds, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_c/group__DDSStatusTypesModule.html
  31. RTI Connext C API: Data Writers, https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_c/group__DDSWriterModule.html
  32. 3.1.2.1. Standard QoS Policies — Fast DDS 2.6.10 documentation, https://fast-dds.docs.eprosima.com/en/2.6.x/fastdds/dds_layer/core/policy/standardQosPolicies.html
  33. DDS::DataWriter Class Reference - Twin Oaks Computing, Inc, https://www.twinoakscomputing.com/documents/refman_html_4.0.10/CoreDX_DDS_CPP_Reference_4.0.10/classDDS_1_1DataWriter.html