Skip to content
4 changes: 2 additions & 2 deletions architecture/server_store_arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ classDiagram
DataStoreUpdater --> IDataStore

PersistentStore --* MemoryStore : PersistentStore contains a MemoryStore
PersistentStore --* TtlTracker
PersistentStore --* ExpirationTracker

IPersistentStoreCore <|-- RedisPersistentStore

Expand Down Expand Up @@ -74,7 +74,7 @@ classDiagram
+const Description() string
}

class TtlTracker{
class ExpirationTracker{
}

class MemoryStore{
Expand Down
4 changes: 4 additions & 0 deletions libs/server-sdk/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ add_library(${LIBNAME}
evaluation/detail/evaluation_stack.cpp
evaluation/detail/semver_operations.cpp
evaluation/detail/timestamp_operations.cpp
data_store/persistent/persistent_data_store.hpp
data_store/persistent/expiration_tracker.hpp
data_store/persistent/persistent_data_store.cpp
data_store/persistent/expiration_tracker.cpp
)

if (MSVC OR (NOT BUILD_SHARED_LIBS))
Expand Down
1 change: 1 addition & 0 deletions libs/server-sdk/src/data_store/dependency_tracker.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "dependency_tracker.hpp"
#include "tagged_data.hpp"

#include <type_traits>

Expand Down
21 changes: 1 addition & 20 deletions libs/server-sdk/src/data_store/dependency_tracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,10 @@
#include <launchdarkly/data_model/segment.hpp>

#include "data_kind.hpp"
#include "tagged_data.hpp"

namespace launchdarkly::server_side::data_store {

/**
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to its own file so it could be re-used.

* Class which can be used to tag a collection with the DataKind that collection
* is for. This is primarily to decrease the complexity of iterating collections
* allowing for a kvp style iteration, but with an array storage container.
* @tparam Storage
*/
template <typename Storage>
class TaggedData {
public:
explicit TaggedData(DataKind kind) : kind_(kind) {}
[[nodiscard]] DataKind Kind() const { return kind_; }
[[nodiscard]] Storage const& Data() const { return storage_; }

[[nodiscard]] Storage& Data() { return storage_; }

private:
DataKind kind_;
Storage storage_;
};

/**
* Class used to maintain a set of dependencies. Each dependency may be either
* a flag or segment.
Expand Down
152 changes: 152 additions & 0 deletions libs/server-sdk/src/data_store/persistent/expiration_tracker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#include "expiration_tracker.hpp"

namespace launchdarkly::server_side::data_store::persistent {

void ExpirationTracker::Add(std::string const& key,
ExpirationTracker::TimePoint expiration) {
unscoped_.insert({key, expiration});
}

void ExpirationTracker::Remove(std::string const& key) {
unscoped_.erase(key);
}

ExpirationTracker::TrackState ExpirationTracker::State(
std::string const& key,
ExpirationTracker::TimePoint current_time) const {
auto item = unscoped_.find(key);
if (item != unscoped_.end()) {
return State(item->second, current_time);
}

return ExpirationTracker::TrackState::kNotTracked;
}

void ExpirationTracker::Add(data_store::DataKind kind,
std::string const& key,
ExpirationTracker::TimePoint expiration) {
scoped_.Set(kind, key, expiration);
}

void ExpirationTracker::Remove(data_store::DataKind kind,
std::string const& key) {
scoped_.Remove(kind, key);
}

ExpirationTracker::TrackState ExpirationTracker::State(
data_store::DataKind kind,
std::string const& key,
ExpirationTracker::TimePoint current_time) const {
auto expiration = scoped_.Get(kind, key);
if (expiration.has_value()) {
return State(expiration.value(), current_time);
}
return ExpirationTracker::TrackState::kNotTracked;
}

void ExpirationTracker::Clear() {
scoped_.Clear();
unscoped_.clear();
}
std::vector<std::pair<std::optional<DataKind>, std::string>>
ExpirationTracker::Prune(ExpirationTracker::TimePoint current_time) {
std::vector<std::pair<std::optional<DataKind>, std::string>> pruned;

// Determine everything to be pruned.
for (auto const& item : unscoped_) {
if (State(item.second, current_time) ==
ExpirationTracker::TrackState::kStale) {
pruned.emplace_back(std::nullopt, item.first);
}
}
for (auto const& scope : scoped_) {
for (auto const& item : scope.Data()) {
if (State(item.second, current_time) ==
ExpirationTracker::TrackState::kStale) {
pruned.emplace_back(scope.Kind(), item.first);
}
}
}

// Do the actual prune.
for (auto const& item : pruned) {
if (item.first.has_value()) {
scoped_.Remove(item.first.value(), item.second);
} else {
unscoped_.erase(item.second);
}
}
return pruned;
}
ExpirationTracker::TrackState ExpirationTracker::State(
ExpirationTracker::TimePoint expiration,
ExpirationTracker::TimePoint current_time) {
if (expiration > current_time) {
return ExpirationTracker::TrackState::kFresh;
}
return ExpirationTracker::TrackState::kStale;
}

void ExpirationTracker::ScopedTtls::Set(
DataKind kind,
std::string const& key,
ExpirationTracker::TimePoint expiration) {
data_[static_cast<std::underlying_type_t<DataKind>>(kind)].Data().insert(
{key, expiration});
}

void ExpirationTracker::ScopedTtls::Remove(DataKind kind,
std::string const& key) {
data_[static_cast<std::underlying_type_t<DataKind>>(kind)].Data().erase(
key);
}

void ExpirationTracker::ScopedTtls::Clear() {
for (auto& scope : data_) {
scope.Data().clear();
}
}

std::optional<ExpirationTracker::TimePoint> ExpirationTracker::ScopedTtls::Get(
DataKind kind,
std::string const& key) const {
auto const& scope =
data_[static_cast<std::underlying_type_t<DataKind>>(kind)];
auto found = scope.Data().find(key);
if (found != scope.Data().end()) {
return found->second;
}
return std::nullopt;
}
ExpirationTracker::ScopedTtls::ScopedTtls()
: data_{
TaggedData<TtlMap>(DataKind::kFlag),
TaggedData<TtlMap>(DataKind::kSegment),
} {}

std::array<TaggedData<ExpirationTracker::TtlMap>, 2>::iterator
ExpirationTracker::ScopedTtls::begin() {
return data_.begin();
}

std::array<TaggedData<ExpirationTracker::TtlMap>, 2>::iterator
ExpirationTracker::ScopedTtls::end() {
return data_.end();
}

std::ostream& operator<<(std::ostream& out,
ExpirationTracker::TrackState const& state) {
switch (state) {
case ExpirationTracker::TrackState::kFresh:
out << "FRESH";
break;
case ExpirationTracker::TrackState::kStale:
out << "STALE";
break;
case ExpirationTracker::TrackState::kNotTracked:
out << "NOT_TRACKED";
break;
}
return out;
}
} // namespace launchdarkly::server_side::data_store::persistent
144 changes: 144 additions & 0 deletions libs/server-sdk/src/data_store/persistent/expiration_tracker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#pragma once

#include <chrono>
#include <functional>
#include <memory>
#include <optional>
#include <string>

#include <launchdarkly/connection.hpp>

#include "../../data_store/data_kind.hpp"
#include "../tagged_data.hpp"

namespace launchdarkly::server_side::data_store::persistent {

class ExpirationTracker {
public:
using TimePoint = std::chrono::time_point<std::chrono::steady_clock>;

/**
* The state of the key in the tracker.
*/
enum class TrackState {
/**
* The key is tracked and the key expiration is in the future.
*/
kFresh,
/**
* The key is tracked and the expiration is either now or in the past.
*/
kStale,
/**
* The key is not being tracked.
*/
kNotTracked
};

/**
* Add an unscoped key to the tracker.
*
* @param key The key to track.
* @param expiration The time that the key expires.
* used.
*/
void Add(std::string const& key, TimePoint expiration);

/**
* Remove an unscoped key from the tracker.
*
* @param key The key to stop tracking.
*/
void Remove(std::string const& key);

/**
* Check the state of an unscoped key.
*
* @param key The key to check.
* @param current_time The current time.
* @return The state of the key.
*/
TrackState State(std::string const& key, TimePoint current_time) const;

/**
* Add a scoped key to the tracker. Will use the specified TTL for the kind.
*
* @param kind The scope (kind) of the key.
* @param key The key to track.
* @param expiration The time that the key expires.
*/
void Add(data_store::DataKind kind,
std::string const& key,
TimePoint expiration);

/**
* Remove a scoped key from the tracker.
*
* @param kind The scope (kind) of the key.
* @param key The key to stop tracking.
*/
void Remove(data_store::DataKind kind, std::string const& key);

/**
* Check the state of a scoped key.
*
* @param kind The scope (kind) of the key.
* @param key The key to check.
* @return The state of the key.
*/
TrackState State(data_store::DataKind kind,
std::string const& key,
TimePoint current_time) const;

/**
* Stop tracking all keys.
*/
void Clear();

/**
* Prune expired keys from the tracker.
* @param current_time The current time.
* @return A list of all the kinds and associated keys that expired.
* Unscoped keys will have std::nullopt as the kind.
*/
std::vector<std::pair<std::optional<DataKind>, std::string>> Prune(
TimePoint current_time);

private:
using TtlMap = std::unordered_map<std::string, TimePoint>;

TtlMap unscoped_;

static ExpirationTracker::TrackState State(
ExpirationTracker::TimePoint expiration,
ExpirationTracker::TimePoint current_time);

class ScopedTtls {
public:
ScopedTtls();

using DataType =
std::array<TaggedData<TtlMap>,
static_cast<std::underlying_type_t<DataKind>>(
DataKind::kKindCount)>;
void Set(DataKind kind, std::string const& key, TimePoint expiration);
void Remove(DataKind kind, std::string const& key);
std::optional<TimePoint> Get(DataKind kind,
std::string const& key) const;
void Clear();

[[nodiscard]] typename DataType::iterator begin();

[[nodiscard]] typename DataType::iterator end();

private:
DataType data_;
};

ScopedTtls scoped_;
};

std::ostream& operator<<(std::ostream& out,
ExpirationTracker::TrackState const& state);

} // namespace launchdarkly::server_side::data_store::persistent
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "persistent_data_store.hpp"

namespace launchdarkly::server_side::data_store::persistent {}
Loading