Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add common persistence. #193

Closed
wants to merge 78 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
c417579
feat: add server-sdk subdirectory
cwaldren-ld Jun 12, 2023
c985539
chore: move ItemDescriptor to internal library (#151)
cwaldren-ld Jun 14, 2023
420d7a2
feat: update event processor to handle context key deduplication (#150)
cwaldren-ld Jun 14, 2023
4f89b44
chore: Merge branch 'main' into server-side
kinyoklion Jun 30, 2023
dd174a2
feat: segment data model (#153)
cwaldren-ld Jun 30, 2023
4954f88
feat: flag data model (#156)
cwaldren-ld Jun 30, 2023
f38e038
Merge branch 'main' into server-side
cwaldren-ld Jul 6, 2023
09d2bd6
feat: add Flag model to SDKDataSet (#159)
cwaldren-ld Jul 6, 2023
8367305
chore: Implement architecture diagram for data store. (#161)
kinyoklion Jul 7, 2023
2781af0
fix: Add various missing headers. (#163)
kinyoklion Jul 7, 2023
90627f2
chore: Implement server data source architecture diagram. (#167)
kinyoklion Jul 11, 2023
f2d96a4
feat: Implement basic in-memory store and change handling. (#165)
kinyoklion Jul 12, 2023
3c12979
chore: Refactor to allow sharing data source status and IDataSource. …
kinyoklion Jul 12, 2023
08aaa66
feat: Implement streaming data source. (#179)
kinyoklion Jul 17, 2023
3692736
chore: Update data store updater use reference. (#181)
kinyoklion Jul 17, 2023
ceec434
Merge branch 'main' into server-side
kinyoklion Jul 17, 2023
99dea86
feat: add ContextKind type to data model (#184)
cwaldren-ld Jul 18, 2023
e5992ef
feat: evaluation engine (#183)
cwaldren-ld Jul 18, 2023
bb75d4b
feat: initial pass of server-side Client object (#176)
cwaldren-ld Jul 19, 2023
662b0b2
feat: Add persistent core interface.
kinyoklion Jul 19, 2023
7bcf2ea
feat: Add expiration tracker.
kinyoklion Jul 19, 2023
17c9dc1
feat: Add expiration tracker.
kinyoklion Jul 19, 2023
27bd749
Constructor/destructor/move/assign.
kinyoklion Jul 19, 2023
4ba9f57
Merge branch 'rlamb/add-persistent-core-interface' into rlamb/add-exp…
kinyoklion Jul 19, 2023
ed60a67
feat: Implement common server persistence.
kinyoklion Jul 19, 2023
02d8de1
Add public specifiers.
kinyoklion Jul 19, 2023
f0e4a44
Merge branch 'rlamb/add-persistent-core-interface' into rlamb/add-exp…
kinyoklion Jul 19, 2023
60ce69e
Merge branch 'rlamb/add-expiration-tracker' into rlamb/common-persist…
kinyoklion Jul 19, 2023
decaf96
Merge updates.
kinyoklion Jul 19, 2023
06e3c93
Start adding defaults.
kinyoklion Jul 19, 2023
a1b7a44
Add extra blank line
kinyoklion Jul 19, 2023
0aeb6e8
Format
kinyoklion Jul 19, 2023
924f435
Merge branch 'rlamb/add-expiration-tracker' into rlamb/common-persist…
kinyoklion Jul 19, 2023
674c343
feat: Add persistent store core interface. (#187)
kinyoklion Jul 19, 2023
0ead705
fix: EvaluationStack should take ownership of key argument (#190)
cwaldren-ld Jul 19, 2023
30d2e21
feat: Add expiration tracker. (#188)
kinyoklion Jul 19, 2023
ea40f2d
Merge branch 'server-side' into rlamb/common-persistent-store
kinyoklion Jul 19, 2023
0c9a6ae
Default persistence config
kinyoklion Jul 19, 2023
6744038
Get basic persistent store structure in place.
kinyoklion Jul 19, 2023
846bfd5
Incremental progress.
kinyoklion Jul 19, 2023
a76b3a8
Serialization progress.
kinyoklion Jul 20, 2023
213acbd
chore: add event processor architectural diagrams (#192)
cwaldren-ld Jul 20, 2023
0b29ea2
feat: Serialize flags and segments. (#194)
kinyoklion Jul 21, 2023
1f44309
Merge branch 'main' into server-side
cwaldren-ld Aug 16, 2023
a592e87
feat: build server SDK in CI (#198)
cwaldren-ld Aug 21, 2023
275bb66
feat: hello-cpp-server (#202)
cwaldren-ld Aug 23, 2023
9b38361
feat: server-side contract tests (#197)
cwaldren-ld Aug 23, 2023
57c29d8
fix: multi-kind user segment targeting (#206)
cwaldren-ld Aug 24, 2023
ef1061e
fix: handle undefined flag variations in fallthrough (#205)
cwaldren-ld Aug 24, 2023
648e202
Merge branch 'main' into server-side
cwaldren-ld Aug 24, 2023
9edacd1
Merge branch 'main' into server-side
cwaldren-ld Aug 25, 2023
09c74dd
remove contract test branch from server.yml
cwaldren-ld Aug 25, 2023
4ccd79f
fix: refactor Variation methods for correctness & clarity (#203)
cwaldren-ld Aug 25, 2023
a7a9660
Merge branch 'main' into server-side
cwaldren-ld Aug 25, 2023
9ec37f6
refactor: push typechecking of variation methods deeper down (#216)
cwaldren-ld Aug 28, 2023
30bfed2
Merge branch 'main' into server-side
cwaldren-ld Aug 28, 2023
62cb864
Merge branch 'main' into server-side
cwaldren-ld Aug 29, 2023
66b4c83
fix: deserializing certain values leads to infinite loop (#224)
cwaldren-ld Aug 29, 2023
eb1b3ed
feat: add DataSourceStatus() and plumb through IClient interface (#217)
cwaldren-ld Aug 30, 2023
b9c03e2
Merge branch 'main' into server-side
cwaldren-ld Aug 30, 2023
371ab00
refactor: move DataSourceStatus ErrorInfo C Bindings into common (#225)
cwaldren-ld Aug 30, 2023
61ddbe7
add Doxygen config and doc.md
cwaldren-ld Aug 30, 2023
aba2473
chore: add Server-side README (#226)
cwaldren-ld Aug 31, 2023
1538a76
fix: ensure flag updates go through data store updater (#228)
cwaldren-ld Aug 31, 2023
7ab8c1d
Merge branch 'main' into server-side
cwaldren-ld Aug 31, 2023
3669ff5
feat: server-side C bindings (#210)
cwaldren-ld Aug 31, 2023
4889549
feat: plumb server side initial backoff delay (#232)
cwaldren-ld Sep 1, 2023
d0ce48d
add story label to remaining test suppressions:
cwaldren-ld Sep 1, 2023
1db6db9
chore: resolve ValidChar function lint/compile warnings (#234)
cwaldren-ld Sep 5, 2023
20abc69
Merge branch 'server-side' into rlamb/common-persistent-store
cwaldren-ld Sep 6, 2023
5f04888
Merge branch 'main' into server-side
cwaldren-ld Sep 11, 2023
fe9e559
Merge branch 'main' into server-side
cwaldren-ld Sep 14, 2023
61a3bef
use async_connect() instead of run() in streaming data source
cwaldren-ld Sep 15, 2023
d3730a8
Merge branch 'server-side' into rlamb/common-persistent-store
cwaldren-ld Sep 15, 2023
8f92208
Merge branch 'main' into rlamb/common-persistent-store
cwaldren-ld Oct 24, 2023
8345ebb
fix merge
cwaldren-ld Oct 24, 2023
1e6f05d
add launchdarkly::unreachable to switch
cwaldren-ld Oct 24, 2023
f2856a9
Merge branch 'main' into rlamb/common-persistent-store
cwaldren-ld Nov 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,71 @@ class PersistenceBuilder<ClientSDK> {
template <>
class PersistenceBuilder<ServerSDK> {
public:
PersistenceBuilder()
: persistence_(Defaults<ServerSDK>::PersistenceConfig()) {}

/**
* Set the core persistence implementation.
*
* @param core The core persistence implementation.
* @return A reference to this builder.
*/
PersistenceBuilder& Core(
std::shared_ptr<persistence::IPersistentStoreCore> core);

/**
* How long something in the cache is considered fresh.
*
* Each item that is cached will have its age tracked. If the age of
* the item exceeds the cache refresh time, then an attempt will be made
* to refresh the item next time it is requested.
*
* When ActiveEviction is set to false then the item will remain cached
* and that cached value will be used if attempts to refresh the value fail.
*
* If ActiveEviction is set to true, then expired items will be periodically
* removed from the cache.
*
* @param cache_refresh_time The time, in seconds, cached data remains
* fresh.
* @return A reference to this builder.
*/
PersistenceBuilder& CacheRefreshTime(
std::chrono::seconds cache_refresh_time) {
persistence_.cache_refresh_time = cache_refresh_time;
return *this;
}

/**
* Enable/disable active eviction.
*
* Defaults to disabled.
* @param active_eviction True to enable.
* @return A reference to this builder.
*/
PersistenceBuilder& ActiveEviction(bool active_eviction) {
persistence_.active_eviction = active_eviction;
return *this;
}

/**
* If active eviction is enabled, then this specifies the time between
* active evictions.
* @param eviction_interval The interval, in seconds, between cache flushes.
* @return A reference to this builder.
*/
PersistenceBuilder& EvictionInterval(
std::chrono::seconds eviction_interval) {
persistence_.eviction_interval = eviction_interval;
return *this;
}

[[nodiscard]] built::Persistence<ServerSDK> Build() const {
return built::Persistence<ServerSDK>();
return persistence_;
}

private:
built::Persistence<ServerSDK> persistence_;
};

} // namespace launchdarkly::config::shared::builders
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#pragma once

#include <chrono>
#include <cstddef>
#include <memory>

#include <launchdarkly/config/shared/sdks.hpp>
#include <launchdarkly/persistence/persistence.hpp>
#include <launchdarkly/persistence/persistent_store_core.hpp>

namespace launchdarkly::config::shared::built {

Expand All @@ -18,6 +21,11 @@ struct Persistence<ClientSDK> {
};

template <>
struct Persistence<ServerSDK> {};
struct Persistence<ServerSDK> {
std::shared_ptr<persistence::IPersistentStoreCore> implementation;
std::chrono::seconds cache_refresh_time;
bool active_eviction;
std::chrono::seconds eviction_interval;
};

} // namespace launchdarkly::config::shared::built
6 changes: 6 additions & 0 deletions libs/common/include/launchdarkly/config/shared/defaults.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <launchdarkly/config/shared/built/data_source_config.hpp>
#include <launchdarkly/config/shared/built/events.hpp>
#include <launchdarkly/config/shared/built/http_properties.hpp>
#include <launchdarkly/config/shared/built/persistence.hpp>
#include <launchdarkly/config/shared/built/service_endpoints.hpp>
#include <launchdarkly/config/shared/sdks.hpp>
#include <launchdarkly/logging/log_level.hpp>
Expand Down Expand Up @@ -38,19 +39,19 @@

static auto Events() -> shared::built::Events {
return {true,
100,

Check warning on line 42 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:42:17 [cppcoreguidelines-avoid-magic-numbers

100 is a magic number; consider replacing it with a named constant
std::chrono::seconds(30),

Check warning on line 43 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:43:38 [cppcoreguidelines-avoid-magic-numbers

30 is a magic number; consider replacing it with a named constant
"/mobile",
false,
AttributeReference::SetType(),
std::chrono::seconds(1),
5,

Check warning on line 48 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:48:17 [cppcoreguidelines-avoid-magic-numbers

5 is a magic number; consider replacing it with a named constant
std::nullopt};
}

static auto HttpProperties() -> shared::built::HttpProperties {
return {std::chrono::seconds{10}, std::chrono::seconds{10},

Check warning on line 53 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:53:38 [cppcoreguidelines-avoid-magic-numbers

10 is a magic number; consider replacing it with a named constant

Check warning on line 53 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:53:64 [cppcoreguidelines-avoid-magic-numbers

10 is a magic number; consider replacing it with a named constant
std::chrono::seconds{10}, std::chrono::seconds{10},

Check warning on line 54 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:54:38 [cppcoreguidelines-avoid-magic-numbers

10 is a magic number; consider replacing it with a named constant

Check warning on line 54 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:54:64 [cppcoreguidelines-avoid-magic-numbers

10 is a magic number; consider replacing it with a named constant
std::map<std::string, std::string>()};
}

Expand All @@ -64,11 +65,11 @@
}

static auto PollingConfig() -> shared::built::PollingConfig<ClientSDK> {
return {std::chrono::minutes(5), "/msdk/evalx/contexts",

Check warning on line 68 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:68:38 [cppcoreguidelines-avoid-magic-numbers

5 is a magic number; consider replacing it with a named constant
"/msdk/evalx/context", std::chrono::minutes(5)};

Check warning on line 69 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:69:61 [cppcoreguidelines-avoid-magic-numbers

5 is a magic number; consider replacing it with a named constant
}

static std::size_t MaxCachedContexts() { return 5; }

Check warning on line 72 in libs/common/include/launchdarkly/config/shared/defaults.hpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/libs/common/include/launchdarkly/config/shared/defaults.hpp:72:53 [cppcoreguidelines-avoid-magic-numbers

5 is a magic number; consider replacing it with a named constant
};

template <>
Expand Down Expand Up @@ -112,6 +113,11 @@
return {std::chrono::seconds{30}, "/sdk/latest-all",
std::chrono::seconds{30}};
}

static auto PersistenceConfig() -> shared::built::Persistence<ServerSDK> {
return {nullptr, std::chrono::seconds{30}, false,
std::chrono::seconds{10}};
}
};

} // namespace launchdarkly::config::shared
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#pragma once

#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>

#include <tl/expected.hpp>

namespace launchdarkly::persistence {

/**
* A versioned item which can be stored in a persistent store.
*/
struct SerializedItemDescriptor {
uint64_t version;

/**
* During an Init/Upsert, when this is true, the serializedItem will
* contain a tombstone representation. If the persistence implementation
* can efficiently store the deletion state, and version, then it may
* choose to discard the item.
*/
bool deleted;

/**
* When reading from a persistent store the serializedItem may be
* std::nullopt for deleted items.
*/
std::optional<std::string> serializedItem;
};

/**
* Represents a namespace of persistent data.
*/
class IPersistentKind {
public:
/**
* The namespace for the data.
*/
[[nodiscard]] virtual std::string const& Namespace() const = 0;

/**
* Deserialize data and return the version of the data.
*
* This is for cases where the persistent store cannot avoid deserializing
* data to determine its version. For instance a Redis store where
* the only columns are the prefixed key and the serialized data.
*
* If the data cannot be deserialized, then 0 will be returned.
*
* @param data The data to deserialize.
* @return The version of the data.
*/
[[nodiscard]] virtual uint64_t Version(std::string const& data) const = 0;

IPersistentKind(IPersistentKind const& item) = delete;
IPersistentKind(IPersistentKind&& item) = delete;
IPersistentKind& operator=(IPersistentKind const&) = delete;
IPersistentKind& operator=(IPersistentKind&&) = delete;
virtual ~IPersistentKind() = default;

protected:
IPersistentKind() = default;
};

/**
* Interface for a data store that holds feature flags and related data in a
* serialized form.
*
* This interface should be used for database integrations, or any other data
* store implementation that stores data in some external service.
* The SDK will take care of converting between its own internal data model and
* a serialized string form; the data store interacts only with the serialized
* form.
*
* The SDK will also provide its own caching layer on top of the persistent data
* store; the data store implementation should not provide caching, but simply
* do every query or update that the SDK tells it to do.
*
* Implementations must be thread-safe.
*/
class IPersistentStoreCore {
public:
enum class InitResult {
/**
* The init operation completed successfully.
*/
kSuccess,

/**
* There was an error with the init operation.
*/
kError,
};

enum class UpsertResult {
/**
* The upsert completed successfully.
*/
kSuccess,

/**
* There was an error with the upsert operation.
*/
kError,

/**
* The upsert did not encounter errors, but the version of the
* existing item was greater than that the version of the upsert item.
*/
kNotUpdated
};

struct Error {
std::string message;
};

using GetResult =
tl::expected<std::optional<SerializedItemDescriptor>, Error>;

using AllResult =
tl::expected<std::unordered_map<std::string, SerializedItemDescriptor>,
Error>;

using ItemKey = std::string;
using KeyItemPair = std::pair<ItemKey, SerializedItemDescriptor>;
using OrderedNamepace = std::vector<KeyItemPair>;
using KindCollectionPair =
std::pair<IPersistentKind const&, OrderedNamepace>;
using OrderedData = std::vector<KindCollectionPair>;

/**
* Overwrites the store's contents with a set of items for each collection.
*
* All previous data should be discarded, regardless of versioning.
*
* The update should be done atomically. If it cannot be done atomically,
* then the store must first add or update each item in the same order that
* they are given in the input data, and then delete any previously stored
* items that were not in the input data.
*
* @param allData The ordered set of data to replace all current data with.
* @return The status of the init operation.
*/
virtual InitResult Init(OrderedData const& allData) = 0;

/**
* Updates or inserts an item in the specified collection. For updates, the
* object will only be updated if the existing version is less than the new
* version.
*
* @param kind The collection kind to use.
* @param itemKey The unique key for the item within the collection.
* @param item The item to insert or update.
*
* @return The status of the operation.
*/
virtual UpsertResult Upsert(IPersistentKind const& kind,
std::string const& itemKey,
SerializedItemDescriptor const& item) = 0;

/**
* Retrieves an item from the specified collection, if available.
*
* @param kind The kind of the item.
* @param itemKey The key for the item.
* @return A serialized item descriptor if the item existed, a std::nullopt
* if the item did not exist, or an error. For a deleted item the serialized
* item descriptor may contain a std::nullopt for the serializedItem.
*/
virtual GetResult Get(IPersistentKind const& kind,
std::string const& itemKey) const = 0;

/**
* Retrieves all items from the specified collection.
*
* If the store contains placeholders for deleted items, it should include
* them in the results, not filter them out.
* @param kind The kind of data to get.
* @return Either all of the items of the type, or an error. If there are
* no items of the specified type, then return an empty collection.
*/
virtual AllResult All(IPersistentKind const& kind) const = 0;

/**
* Returns true if this store has been initialized.
*
* In a shared data store, the implementation should be able to detect this
* state even if Init was called in a different process, i.e. it must query
* the underlying data store in some way. The method does not need to worry
* about caching this value; the SDK will call it rarely.
*
* @return True if the store has been initialized.
*/
virtual bool Initialized() const = 0;

/**
* A short description of the store, for instance "Redis". May be used
* in diagnostic information and logging.
*
* @return A short description of the sore.
*/
virtual std::string const& Description() const = 0;

IPersistentStoreCore(IPersistentStoreCore const& item) = delete;
IPersistentStoreCore(IPersistentStoreCore&& item) = delete;
IPersistentStoreCore& operator=(IPersistentStoreCore const&) = delete;
IPersistentStoreCore& operator=(IPersistentStoreCore&&) = delete;
virtual ~IPersistentStoreCore() = default;

protected:
IPersistentStoreCore() = default;
};
} // namespace launchdarkly::persistence
2 changes: 1 addition & 1 deletion libs/server-sdk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ endif ()
#set(CMAKE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
#set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_FILES})

# Needed to fetch external dependencies.
# Needed to fetch external dependencies.
include(FetchContent)

# Needed to parse RFC3339 dates in flag rules.
Expand Down
Loading
Loading