shard session_store
This commit is contained in:
parent
652053128d
commit
7e4c8d12cc
@ -8,20 +8,27 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <atomic>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
class SessionStore {
|
class SessionStore {
|
||||||
private:
|
private:
|
||||||
static constexpr size_t SHARD_COUNT = 16;
|
static constexpr size_t SHARD_COUNT = 16;
|
||||||
|
static constexpr size_t MAX_SESSIONS_PER_SHARD = 64;
|
||||||
|
static constexpr size_t MAX_SESSION_DATA_SIZE = 16;
|
||||||
|
static constexpr size_t MAX_VALUE_SIZE = 1024;
|
||||||
static constexpr auto SESSION_TIMEOUT = std::chrono::hours(24);
|
static constexpr auto SESSION_TIMEOUT = std::chrono::hours(24);
|
||||||
|
|
||||||
struct SessionData {
|
struct SessionData {
|
||||||
std::unordered_map<std::string, std::string> data;
|
std::unordered_map<std::string, std::string> data;
|
||||||
std::chrono::steady_clock::time_point last_access;
|
std::chrono::steady_clock::time_point last_access;
|
||||||
|
std::list<std::string>::iterator lru_iter;
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<std::mutex, SHARD_COUNT> mutexes_;
|
std::array<std::mutex, SHARD_COUNT> mutexes_;
|
||||||
std::array<std::unordered_map<std::string, SessionData>, SHARD_COUNT> shards_;
|
std::array<std::unordered_map<std::string, SessionData>, SHARD_COUNT> shards_;
|
||||||
|
std::array<std::list<std::string>, SHARD_COUNT> lru_lists_;
|
||||||
KeyValueStore store_;
|
KeyValueStore store_;
|
||||||
std::mt19937 rng_;
|
std::mt19937 rng_;
|
||||||
|
|
||||||
@ -29,24 +36,66 @@ private:
|
|||||||
return std::hash<std::string>{}(id) % SHARD_COUNT;
|
return std::hash<std::string>{}(id) % SHARD_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void evict_lru_session(size_t shard) {
|
||||||
|
auto& shard_map = shards_[shard];
|
||||||
|
auto& lru_list = lru_lists_[shard];
|
||||||
|
|
||||||
|
if (lru_list.empty()) return;
|
||||||
|
|
||||||
|
std::string oldest_id = lru_list.back();
|
||||||
|
lru_list.pop_back();
|
||||||
|
|
||||||
|
auto it = shard_map.find(oldest_id);
|
||||||
|
if (it != shard_map.end()) {
|
||||||
|
// Save dirty session before eviction
|
||||||
|
if (it->second.dirty) {
|
||||||
|
for (const auto& [key, value] : it->second.data) {
|
||||||
|
store_.set(oldest_id + ":" + key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shard_map.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_session(const std::string& id, size_t shard) {
|
||||||
|
auto& session = shards_[shard][id];
|
||||||
|
auto& lru_list = lru_lists_[shard];
|
||||||
|
|
||||||
|
// Move to front of LRU
|
||||||
|
lru_list.erase(session.lru_iter);
|
||||||
|
lru_list.push_front(id);
|
||||||
|
session.lru_iter = lru_list.begin();
|
||||||
|
session.last_access = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
void lazy_load_session(const std::string& id, size_t shard) {
|
void lazy_load_session(const std::string& id, size_t shard) {
|
||||||
auto& shard_map = shards_[shard];
|
auto& shard_map = shards_[shard];
|
||||||
if (shard_map.find(id) != shard_map.end()) return;
|
if (shard_map.find(id) != shard_map.end()) return;
|
||||||
|
|
||||||
// Load from persistent store
|
// Check session limit
|
||||||
|
if (shard_map.size() >= MAX_SESSIONS_PER_SHARD) {
|
||||||
|
evict_lru_session(shard);
|
||||||
|
}
|
||||||
|
|
||||||
SessionData session;
|
SessionData session;
|
||||||
session.last_access = std::chrono::steady_clock::now();
|
session.last_access = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// Try to load existing session data
|
// Load from persistent store
|
||||||
for (const auto& [key, value] : store_.get_all()) {
|
for (const auto& [key, value] : store_.get_all()) {
|
||||||
if (key.starts_with(id + ":")) {
|
if (key.starts_with(id + ":")) {
|
||||||
std::string session_key = key.substr(id.length() + 1);
|
std::string session_key = key.substr(id.length() + 1);
|
||||||
if (auto* str_val = std::get_if<std::string>(&value)) {
|
if (auto* str_val = std::get_if<std::string>(&value)) {
|
||||||
session.data[session_key] = *str_val;
|
if (session.data.size() < MAX_SESSION_DATA_SIZE) {
|
||||||
|
session.data[session_key] = *str_val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add to LRU
|
||||||
|
lru_lists_[shard].push_front(id);
|
||||||
|
session.lru_iter = lru_lists_[shard].begin();
|
||||||
|
|
||||||
shard_map[id] = std::move(session);
|
shard_map[id] = std::move(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,9 +105,11 @@ private:
|
|||||||
for (size_t i = 0; i < SHARD_COUNT; ++i) {
|
for (size_t i = 0; i < SHARD_COUNT; ++i) {
|
||||||
std::lock_guard<std::mutex> lock(mutexes_[i]);
|
std::lock_guard<std::mutex> lock(mutexes_[i]);
|
||||||
auto& shard = shards_[i];
|
auto& shard = shards_[i];
|
||||||
|
auto& lru_list = lru_lists_[i];
|
||||||
|
|
||||||
for (auto it = shard.begin(); it != shard.end();) {
|
for (auto it = shard.begin(); it != shard.end();) {
|
||||||
if (now - it->second.last_access > SESSION_TIMEOUT) {
|
if (now - it->second.last_access > SESSION_TIMEOUT) {
|
||||||
|
lru_list.erase(it->second.lru_iter);
|
||||||
it = shard.erase(it);
|
it = shard.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
@ -73,7 +124,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool needs_session(const Request& req) const {
|
bool needs_session(const Request& req) const {
|
||||||
// Skip sessions for static content
|
|
||||||
if (req.path.starts_with("/static") ||
|
if (req.path.starts_with("/static") ||
|
||||||
req.path.starts_with("/assets") ||
|
req.path.starts_with("/assets") ||
|
||||||
req.path.ends_with(".css") ||
|
req.path.ends_with(".css") ||
|
||||||
@ -90,7 +140,6 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need sessions for API and admin routes
|
|
||||||
return req.path.starts_with("/api") ||
|
return req.path.starts_with("/api") ||
|
||||||
req.path.starts_with("/admin") ||
|
req.path.starts_with("/admin") ||
|
||||||
req.method == HttpMethod::POST ||
|
req.method == HttpMethod::POST ||
|
||||||
@ -103,11 +152,21 @@ public:
|
|||||||
size_t shard = get_shard(id);
|
size_t shard = get_shard(id);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
||||||
|
|
||||||
|
// Check session limit
|
||||||
|
if (shards_[shard].size() >= MAX_SESSIONS_PER_SHARD) {
|
||||||
|
evict_lru_session(shard);
|
||||||
|
}
|
||||||
|
|
||||||
SessionData session;
|
SessionData session;
|
||||||
session.last_access = std::chrono::steady_clock::now();
|
session.last_access = std::chrono::steady_clock::now();
|
||||||
session.dirty = true;
|
session.dirty = true;
|
||||||
shards_[shard][id] = std::move(session);
|
|
||||||
|
|
||||||
|
// Add to LRU
|
||||||
|
lru_lists_[shard].push_front(id);
|
||||||
|
session.lru_iter = lru_lists_[shard].begin();
|
||||||
|
|
||||||
|
shards_[shard][id] = std::move(session);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,21 +179,30 @@ public:
|
|||||||
auto it = shards_[shard].find(id);
|
auto it = shards_[shard].find(id);
|
||||||
if (it == shards_[shard].end()) return std::nullopt;
|
if (it == shards_[shard].end()) return std::nullopt;
|
||||||
|
|
||||||
it->second.last_access = std::chrono::steady_clock::now();
|
touch_session(id, shard);
|
||||||
|
|
||||||
auto data_it = it->second.data.find(key);
|
auto data_it = it->second.data.find(key);
|
||||||
return data_it != it->second.data.end() ?
|
return data_it != it->second.data.end() ?
|
||||||
std::optional<std::string>{data_it->second} : std::nullopt;
|
std::optional<std::string>{data_it->second} : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(const std::string& id, const std::string& key, const std::string& value) {
|
void set(const std::string& id, const std::string& key, const std::string& value) {
|
||||||
|
if (value.size() > MAX_VALUE_SIZE) return;
|
||||||
|
|
||||||
size_t shard = get_shard(id);
|
size_t shard = get_shard(id);
|
||||||
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
||||||
|
|
||||||
lazy_load_session(id, shard);
|
lazy_load_session(id, shard);
|
||||||
|
|
||||||
auto& session = shards_[shard][id];
|
auto& session = shards_[shard][id];
|
||||||
|
|
||||||
|
// Check data limit
|
||||||
|
if (session.data.size() >= MAX_SESSION_DATA_SIZE && session.data.find(key) == session.data.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
session.data[key] = value;
|
session.data[key] = value;
|
||||||
session.last_access = std::chrono::steady_clock::now();
|
touch_session(id, shard);
|
||||||
session.dirty = true;
|
session.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,18 +210,20 @@ public:
|
|||||||
size_t shard = get_shard(id);
|
size_t shard = get_shard(id);
|
||||||
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
std::lock_guard<std::mutex> lock(mutexes_[shard]);
|
||||||
|
|
||||||
bool found = shards_[shard].erase(id) > 0;
|
auto it = shards_[shard].find(id);
|
||||||
|
if (it == shards_[shard].end()) return false;
|
||||||
|
|
||||||
|
lru_lists_[shard].erase(it->second.lru_iter);
|
||||||
|
shards_[shard].erase(it);
|
||||||
|
|
||||||
// Remove from persistent store
|
// Remove from persistent store
|
||||||
if (found) {
|
for (const auto& [key, value] : store_.get_all()) {
|
||||||
for (const auto& [key, value] : store_.get_all()) {
|
if (key.starts_with(id + ":")) {
|
||||||
if (key.starts_with(id + ":")) {
|
store_.del(key);
|
||||||
store_.del(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return found;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size() {
|
size_t size() {
|
||||||
@ -171,11 +241,9 @@ public:
|
|||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
store_.load();
|
store_.load();
|
||||||
// Sessions loaded lazily on first access
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void save() {
|
void save() {
|
||||||
// Save only dirty sessions
|
|
||||||
for (size_t i = 0; i < SHARD_COUNT; ++i) {
|
for (size_t i = 0; i < SHARD_COUNT; ++i) {
|
||||||
std::lock_guard<std::mutex> lock(mutexes_[i]);
|
std::lock_guard<std::mutex> lock(mutexes_[i]);
|
||||||
|
|
||||||
@ -193,7 +261,6 @@ public:
|
|||||||
store_.save();
|
store_.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use existing cookie helpers
|
|
||||||
static std::string_view extract_session_id(const Request& req) {
|
static std::string_view extract_session_id(const Request& req) {
|
||||||
return req.get_cookie("session_id");
|
return req.get_cookie("session_id");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user