Quick Start With C++
This quick start guide will walk you through the process of installing and using the ReductStore C++ Client SDK to read and write data to a ReductStore instance.
Installing the C++ SDK​
The ReductStore C++ SDK requires the following dependencies:
- GCC 11.2 or higher (support C++20)
- CMake 3.18 or higher
- ZLib
- OpenSSL 1.1 or 3.0
- Conan 1.58 (optionally)
To install the ReductStore C++ SDK, follow these steps:
git clone https://github.com/reductstore/reduct-cpp.git
cd reduct-cpp
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
sudo cmake --build . --target install
CMake tries to use the conan
package manager if it is installed. If it isn't, it downloads all the dependencies by using
FetchContent. To use ReductStore SDK you need only to use find_pacakge
in your cmake lists:
After installing the SDK, you need to link the ReductCpp
library to your project. The following example shows how
to do this using CMake:
cmake_minimum_required(VERSION 3.18)
project(ReductCppExample)
set(CMAKE_CXX_STANDARD 20)
find_package(ZLIB)
find_package(OpenSSL)
find_package(ReductCpp)
add_executable(quick_start quick_start.cc)
target_link_libraries(quick_start ${REDUCT_CPP_LIBRARIES} ${ZLIB_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto)
Running ReductStore​
If you don't already have a ReductStore instance running, you can easily spin up one as a Docker container. To do this, run the following command:
docker run -p 8383:8383 -e RS_API_TOKEN="my-token" reduct/store:latest
This will start a ReductStore instance listening on port 8383 on your local machine. The RS_API_TOKEN
environment variable is used to authenticate requests to the ReductStore instance. You can set it to any value you like, but you will need to use the same value when creating a Client
object in your code.
If Docker is not an option, you can also download the ReductStore binaries. Check the Download Page.
Hello World Example​
Now when you have the SDK installed and a ReductStore instance running, you can start using the SDK to interact with the ReductStore database. Here is an example of using the SDK to perform basic operations on a ReductStore instance:
#include <reduct/client.h>
#include <iostream>
#include <cassert>
using reduct::IBucket;
using reduct::IClient;
using reduct::Error;
using sec = std::chrono::seconds;
int main() {
// 1. Create a ReductStore client
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
// 2. Get or create a bucket with 1Gb quota
auto [bucket, create_err] = client->GetOrCreateBucket("my-bucket", {
.quota_type = IBucket::QuotaType::kFifo,
.quota_size = 1'000'000'000
});
if (create_err) {
std::cerr << "Error: " << create_err;
return -1;
}
// 3. Write some data with timestamps and labels to the 'entry-1' entry
IBucket::Time start = IBucket::Time::clock::now();
auto write_err = bucket->Write("sensor-1", {
.timestamp = start,
.labels = {{"score", "10"}}
}, [](auto rec) { rec->WriteAll("<Blob data>"); });
assert(write_err == Error::kOk);
write_err = bucket->Write("sensor-1", {
.timestamp = start + sec(1),
.labels = {{"score", "20"}}
}, [](auto rec) { rec->WriteAll("<Blob data>"); });
assert(write_err == Error::kOk);
// 4. Query the data by time range and condition
auto err = bucket->Query("sensor-1", start, start + sec(2), {
.when = R"({"&score": {"$gt": 10}})"
}, [](auto &&record) {
std::cout << "Timestamp: " << record.timestamp.time_since_epoch().count() << std::endl;
std::cout << "Content Length: " << record.size << std::endl;
auto [blob, read_err] = record.ReadAll();
if (!read_err) {
std::cout << "Read blob: " << blob << std::endl;
}
return true;
});
if (err) {
std::cerr << "Error: " << err;
return -1;
}
// 5. End
return 0;
}
Let's break down what this example is doing.
Creating a Client​
Before you can interact with a ReductStore instance, you must create a Client
object that represents a connection to the ReductStore instance.
To create a ReductStore client, you can use the IClient::Build
fabric method from the reduct
namespace. Pass the URL of the
ReductStore instance you want to connect to as an argument to the method.
auto client = IClient::Build("http://127.0.0.1:8383", {
.api_token = "my-token"
});
Creating a Bucket​
ReductStore organizes data into buckets, each of which has its own quota and settings. It's a necessary step to create a bucket before writing data to it. You can read more about buckets in the Buckets Guide, but for now, let's just create one.
To create a bucket, you should use the GetOrCreateBucket
method on a Client
instance. Pass the name of the bucket
you want to create as an argument, along with settings. If the bucket already exists, the GetOrCreateBucket
method will return it:
auto [bucket, create_err] = client->GetOrCreateBucket("my-bucket", {
.quota_type = IBucket::QuotaType::kFifo,
.quota_size = 1'000'000'000
});
if (create_err) {
std::cerr << "Error: " << create_err;
return -1;
}
In this example we create a bucket with a FIFO quota of 1GB. This means that the oldest data will be deleted when the bucket reaches 1GB.
Data Ingestion​
Time series data is stored in entries within a bucket. An entry is a collection of records with unique timestamps. It must have a unique name within the bucket and usually represents a data source, such as a vibration sensor or a CV camera.
To write a timestamped record to an entry in a bucket, you should use the Write
method on a Bucket
instance.
Pass the name of the entry you want to write to as an argument, along with the timestamp you want to write.
Additionally, you can provide labels to the record to make it easier to query later.
The Write
method will call a callback function with the WritableRecord
instance to write to the entry:
IBucket::Time start = IBucket::Time::clock::now();
auto write_err = bucket->Write("sensor-1", {
.timestamp = start,
.labels = {{"score", "10"}}
}, [](auto rec) { rec->WriteAll("<Blob data>"); });
assert(write_err == Error::kOk);
write_err = bucket->Write("sensor-1", {
.timestamp = start + sec(1),
.labels = {{"score", "20"}}
}, [](auto rec) { rec->WriteAll("<Blob data>"); });
assert(write_err == Error::kOk);
This is the simplest case of writing data with the SDK. You can also stream data in chunks and annotate records with many labels. You can find more information and examples in the Data Ingestion Guide.
Data Querying​
Usually, we don't read a particular record by its timestamp, but query records in a time range.
To iterate over all records in a given time range, you should use the Query
method on a bucket instance. Pass the
name of the entry to iterate over, and start
and stop
arguments to specify the time interval.You can also
provide a when
condition to filter records based on labels. Read more about the query syntax in the Conditional Query Reference.
The method will return a callback function with the ReadableRecord
instance for each record in the time range:
auto err = bucket->Query("sensor-1", start, start + sec(2), {
.when = R"({"&score": {"$gt": 10}})"
}, [](auto &&record) {
std::cout << "Timestamp: " << record.timestamp.time_since_epoch().count() << std::endl;
std::cout << "Content Length: " << record.size << std::endl;
auto [blob, read_err] = record.ReadAll();
if (!read_err) {
std::cout << "Read blob: " << blob << std::endl;
}
return true;
});
if (err) {
std::cerr << "Error: " << err;
return -1;
}
The query method has many parameters for filtering and returning sample records. For more information and examples, see the Data Querying Guide.
Next Steps​
As you can see to get started with the Client SDK is very easy. However,it doesn't cover all the features of the SDK and the database. Check our Guides to learn more about the ReductStore features and how to use them.