7. Persistence Service

Using default QoS, the DataWriter history is only available for DataReader throughout the DataWriter’s life. This means that the history does not persist between DataWriter initializations and therefore it is on an empty state on DataWriter creation. Similarly, the DataReader history does not persist the DataReader’s life, thus also being empty on DataReader creation. However, eProsima Fast DDS offers the possibility to configure the DataWriter’s history to be stored in a persistent database, so that the DataWriter can load its history from it on creation. Furthermore, DataReaders can be configured to store the last notified change in the database, so that they can recover their state on creation.

This mechanism allows recovering a previous state on starting the Data Distribution Service, thus adding robustness to applications in the case of, for example, unexpected shutdowns. Configuring the persistence service, DataWriters and DataReaders can resume their operation from the state in which they were when the shutdown occurred.

Note

Mind that DataReaders do not store their history into the database, but rather the last notified change from the DataWriter. This means that they will resume operation where they left, but they will not have the previous information, since that was already notified to the application.

7.1. Configuration

The configuration of the persistence service is accomplished by setting of the appropriate DataWriter and DataReader DurabilityQosPolicy, and by specifying the suitable properties for each entity’s (DomainParticipant, DataWriter, or DataReader) PropertyPolicyQos.

  • For the Persistence Service to have any effect, the DurabilityQosPolicyKind needs to be set to TRANSIENT_DURABILITY_QOS.

  • A persistence identifier (Guid_t) must be set for the entity using the property dds.persistence.guid. This identifier is used to load the appropriate data from the database, and also to synchronize DataWriter and DataReader between restarts. The GUID consists of 16 bytes separated into two groups:

    The persistence identifier is specified using a string of 12 dot-separated bytes, expressed in hexadecimal base, followed by a vertical bar separator (|) and another 4 dot-separated bytes, also expressed in hexadecimal base (see Example). For selecting an appropriate GUID for the DataReader and DataWriter, please refer to RTPS standard (section 9.3.1 The Globally Unique Identifier (GUID)).

  • A persistence plugin must be configured for managing the database using property dds.persistence.plugin (see PERSISTENCE:SQLITE3 built-in plugin):

7.2. PERSISTENCE:SQLITE3 built-in plugin

This plugin provides persistence through a local database file using SQLite3 API. To activate the plugin, dds.persistence.plugin property must be added to the PropertyPolicyQos of the DomainParticipant, DataWriter, or DataReader with value builtin.SQLITE3. Furthermore, dds.persistence.sqlite3.filename property must be added to the entities PropertyPolicyQos, specifying the database file name. These properties are summarized in the following table:

Persistence::SQLITE3 configuration properties

Property name

Property value

dds.persistence.plugin

builtin.SQLITE3

dds.persistence.sqlite3.filename

Name of the file used for persistent storage.
Default value: persistence.db

Note

To avoid undesired delays caused by concurrent access to the SQLite3 database, it is advisable to specify a different database file for each DataWriter and DataReader.

Important

The plugin set in the PropertyPolicyQos of DomainParticipant only applies if that of the DataWriter/DataReader does no exist or is invalid.

7.3. Example

This example shows how to configure the persistence service using PERSISTENCE:SQLITE3 built-in plugin plugin both from C++ and using eProsima Fast DDS XML profile files (see XML profiles).

/*
 * In order for this example to be self-contained, all the entities are created programatically, including the data
 * type and type support. This has been done using Fast DDS Dynamic Types API, but it could be substituted with a
 * Fast DDS-Gen generated type support if an IDL file is available. The Dynamic Type created here is the equivalent
 * of the following IDL:
 *
 *     struct persistence_topic_type
 *     {
 *         unsigned long index;
 *         string message;
 *     };
 */

// Configure persistence service plugin for DomainParticipant
DomainParticipantQos pqos;
pqos.properties().properties().emplace_back("dds.persistence.plugin", "builtin.SQLITE3");
pqos.properties().properties().emplace_back("dds.persistence.sqlite3.filename", "persistence.db");
DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, pqos);

/********************************************************************************************************
* CREATE TYPE AND TYPE SUPPORT
*********************************************************************************************************
* This part could be replaced if IDL file and Fast DDS-Gen are available.
* The type is created with name "persistence_topic_type"
* Additionally, create a data object and populate it, just to show how to do it
********************************************************************************************************/
// Create a struct builder for a type with name "persistence_topic_type"
const std::string topic_type_name = "persistence_topic_type";
eprosima::fastrtps::types::DynamicTypeBuilder_ptr struct_type_builder(
    eprosima::fastrtps::types::DynamicTypeBuilderFactory::get_instance()->create_struct_builder());
struct_type_builder->set_name(topic_type_name);

// The type consists of two members, and index and a message. Add members to the struct.
struct_type_builder->add_member(0, "index",
        eprosima::fastrtps::types::DynamicTypeBuilderFactory::get_instance()->create_uint32_type());
struct_type_builder->add_member(1, "message",
        eprosima::fastrtps::types::DynamicTypeBuilderFactory::get_instance()->create_string_type());

// Build the type
eprosima::fastrtps::types::DynamicType_ptr dyn_type_ptr = struct_type_builder->build();

// Create type support and register the type
TypeSupport type_support(new eprosima::fastrtps::types::DynamicPubSubType(dyn_type_ptr));
type_support.register_type(participant);

// Create data sample a populate data. This is to be used when calling `writer->write()`
eprosima::fastrtps::types::DynamicData* dyn_helloworld;
dyn_helloworld = eprosima::fastrtps::types::DynamicDataFactory::get_instance()->create_data(dyn_type_ptr);
dyn_helloworld->set_uint32_value(0, 0);
dyn_helloworld->set_string_value("HelloWorld", 1);
/********************************************************************************************************
* END CREATE TYPE AND TYPE SUPPORT
********************************************************************************************************/

// Create a topic
Topic* topic = participant->create_topic("persistence_topic_name", topic_type_name, TOPIC_QOS_DEFAULT);

// Create a publisher and a subscriber with default QoS
Publisher* publisher = participant->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
Subscriber* subscriber = participant->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);

// Configure DataWriter's durability and persistence GUID so it can use the persistence service
DataWriterQos dwqos = DATAWRITER_QOS_DEFAULT;
dwqos.durability().kind = TRANSIENT_DURABILITY_QOS;
dwqos.properties().properties().emplace_back("dds.persistence.guid",
        "77.72.69.74.65.72.5f.70.65.72.73.5f|67.75.69.64");
DataWriter* writer = publisher->create_datawriter(topic, dwqos);

// Configure DataReaders's durability and persistence GUID so it can use the persistence service
DataReaderQos drqos = DATAREADER_QOS_DEFAULT;
drqos.durability().kind = TRANSIENT_DURABILITY_QOS;
drqos.properties().properties().emplace_back("dds.persistence.guid",
        "72.65.61.64.65.72.5f.70.65.72.73.5f|67.75.69.64");
DataReader* reader = subscriber->create_datareader(topic, drqos);

Note

For instructions on how to create DomainParticipants, DataReaders, and DataWriters, please refer to Profile based creation of a DomainParticipant, Profile based creation of a DataWriter, and Profile based creation of a DataReader respectively.