229 lines
7.3 KiB
C++
229 lines
7.3 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <queue>
|
|
#include <shared_mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "mutex.h"
|
|
#include "shm_helper.h"
|
|
#include "types.h"
|
|
|
|
namespace mooncake {
|
|
|
|
/**
|
|
* @brief Memory block metadata for hot cache.
|
|
*/
|
|
struct HotMemBlock {
|
|
void* addr;
|
|
size_t size;
|
|
std::atomic<int> ref_count;
|
|
std::string key_;
|
|
std::atomic<bool> accessed{false}; // Deferred LRU touch flag
|
|
HotMemBlock() : addr(nullptr), size(0), ref_count(0) {}
|
|
};
|
|
|
|
/**
|
|
* @brief Local hot cache used for hot kv cache in the distributed store.
|
|
*/
|
|
class LocalHotCache {
|
|
public:
|
|
/**
|
|
* @brief Construct a LocalHotCache.
|
|
* @param total_size_bytes Desired total local hot cache size in bytes.
|
|
* @param block_size_bytes Block size in bytes. If 0, uses default 16MB.
|
|
* @param use_shm If true, allocate via memfd for cross-process sharing.
|
|
*/
|
|
LocalHotCache(size_t total_size_bytes, size_t block_size_bytes = 0,
|
|
bool use_shm = false);
|
|
|
|
/**
|
|
* @brief Destructor.
|
|
*/
|
|
~LocalHotCache();
|
|
|
|
/**
|
|
* @brief Insert a populated block into the cache.
|
|
* Takes ownership of the block and inserts it into the LRU.
|
|
* The block must have been obtained from GetFreeBlock() and have key_ set.
|
|
* If the key already exists (race condition) or is empty, the block is
|
|
* cleared and returned to the pool as a free block.
|
|
* @param block The block containing the data and key.
|
|
* @return true if inserted successfully, false if race condition or error.
|
|
*/
|
|
bool PutHotKey(HotMemBlock* block);
|
|
|
|
/**
|
|
* @brief Check if the key exists in cache.
|
|
* @param key Cache key: {request key}
|
|
*/
|
|
bool HasHotKey(const std::string& key) const;
|
|
|
|
/**
|
|
* @brief Get the underlying HotMemBlock pointer and touch LRU.
|
|
* The block will be marked as in_use to prevent it from being reused
|
|
* until ReleaseHotKey is called.
|
|
* @param key : {request key}
|
|
* @return HotMemBlock* on hit; nullptr on miss.
|
|
*/
|
|
HotMemBlock* GetHotKey(const std::string& key);
|
|
|
|
/**
|
|
* @brief Release a hot key block, marking it as no longer in use.
|
|
* This should be called after the block is no longer being read from.
|
|
* @param key : {request key}
|
|
*/
|
|
void ReleaseHotKey(const std::string& key);
|
|
|
|
/**
|
|
* @brief Touch a key if it exists in the hot cache.
|
|
* Only touches the LRU, does not modify data or allocation.
|
|
* @param key Cache key.
|
|
* @return true if key exists and was touched, false otherwise.
|
|
*/
|
|
bool TouchHotKey(const std::string& key);
|
|
|
|
/**
|
|
* @brief Get a free block for writing.
|
|
* Detaches a block from the LRU tail (evicting if necessary) and returns
|
|
* it. The returned block is owned by the caller and must be returned via
|
|
* PutHotKey.
|
|
* @return Pointer to a HotMemBlock, or nullptr if no block is available.
|
|
*/
|
|
HotMemBlock* GetFreeBlock();
|
|
|
|
/**
|
|
* @brief Get the number of cache blocks available.
|
|
* @return Number of blocks in LRU queue (cache size).
|
|
*/
|
|
size_t GetCacheSize() const;
|
|
|
|
/**
|
|
* @brief Get the block size used by this cache.
|
|
* @return Block size in bytes.
|
|
*/
|
|
size_t GetBlockSize() const { return block_size_; }
|
|
|
|
/**
|
|
* @brief Get the shm segment backing this cache.
|
|
* Only valid when constructed with use_shm=true.
|
|
* @return shared_ptr to the ShmSegment, or nullptr if shm is disabled.
|
|
*/
|
|
std::shared_ptr<ShmHelper::ShmSegment> GetShmSegment() const {
|
|
return shm_segment_;
|
|
}
|
|
|
|
/**
|
|
* @brief Whether this cache was allocated in shm mode (cross-process).
|
|
*/
|
|
bool IsShm() const { return use_shm_; }
|
|
|
|
/**
|
|
* @brief Compute offset of a block address relative to the bulk base.
|
|
* Used by dummy clients to translate to their own mmap'd address.
|
|
* @return offset in bytes, or SIZE_MAX if addr is not in the bulk region.
|
|
*/
|
|
size_t GetBlockOffset(const void* addr) const;
|
|
|
|
private:
|
|
// Drain deferred LRU touches: splice accessed blocks to front
|
|
void drainDeferredTouches();
|
|
|
|
size_t block_size_; // Actual block size used by this cache
|
|
|
|
// All blocks owned by this cache (auto-cleaned on destruction)
|
|
std::vector<std::unique_ptr<HotMemBlock>> blocks_;
|
|
|
|
// Bulk allocated memory pointer (nullptr if allocation failed)
|
|
void* bulk_memory_standard_;
|
|
size_t bulk_memory_size_ = 0;
|
|
|
|
// Shared memory segment (non-null only when use_shm=true)
|
|
std::shared_ptr<ShmHelper::ShmSegment> shm_segment_;
|
|
bool use_shm_ = false;
|
|
|
|
mutable std::shared_mutex lru_mutex_;
|
|
std::list<HotMemBlock*> lru_queue_ GUARDED_BY(lru_mutex_); // prefilled LRU
|
|
// key -> iterator of lru_queue_
|
|
std::unordered_map<std::string, std::list<HotMemBlock*>::iterator>
|
|
key_to_lru_it_ GUARDED_BY(lru_mutex_);
|
|
};
|
|
|
|
/**
|
|
* @brief Task for async hot cache put operation.
|
|
*/
|
|
struct HotCachePutTask {
|
|
std::string key;
|
|
HotMemBlock* block; // Pointer to the allocated block
|
|
size_t size;
|
|
std::shared_ptr<LocalHotCache> hot_cache;
|
|
|
|
// Default constructor for empty task
|
|
HotCachePutTask() : block(nullptr), size(0), hot_cache(nullptr) {}
|
|
|
|
HotCachePutTask(const std::string& k, const Slice& slice, HotMemBlock* blk,
|
|
std::shared_ptr<LocalHotCache> cache)
|
|
: key(k), block(blk), size(slice.size), hot_cache(std::move(cache)) {
|
|
// No data copy here; memcpy is done by SubmitPutTask into block->addr.
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Handler for asynchronously executing PutHotKey operations.
|
|
*/
|
|
class LocalHotCacheHandler {
|
|
public:
|
|
/**
|
|
* @brief Construct a LocalHotCacheHandler.
|
|
* @param hot_cache Pointer to LocalHotCache instance (can be null if cache
|
|
* disabled).
|
|
* @param num_worker_threads Number of worker threads for async processing
|
|
* (default: 2).
|
|
* @param max_queue_capacity Maximum task queue capacity (default: 1024).
|
|
*/
|
|
LocalHotCacheHandler(std::shared_ptr<LocalHotCache> hot_cache,
|
|
size_t num_worker_threads = 2,
|
|
size_t max_queue_capacity = 1024);
|
|
|
|
~LocalHotCacheHandler();
|
|
|
|
// Non-copyable, non-movable
|
|
LocalHotCacheHandler(const LocalHotCacheHandler&) = delete;
|
|
LocalHotCacheHandler& operator=(const LocalHotCacheHandler&) = delete;
|
|
LocalHotCacheHandler(LocalHotCacheHandler&&) = delete;
|
|
LocalHotCacheHandler& operator=(LocalHotCacheHandler&&) = delete;
|
|
|
|
/**
|
|
* @brief Submit an async task to put a slice into the hot cache.
|
|
*
|
|
* Data is copied into the cache block synchronously within this call.
|
|
* The caller may free the source slice memory after this function returns.
|
|
* @param key Cache key: {object key}
|
|
* @param slice Source slice to cache.
|
|
* @return true if task was successfully submitted, false otherwise (e.g.,
|
|
* hot_cache_ is null or handler is shutdown).
|
|
*/
|
|
bool SubmitPutTask(const std::string& key, const Slice& slice);
|
|
|
|
private:
|
|
void workerThread();
|
|
|
|
std::shared_ptr<LocalHotCache> hot_cache_;
|
|
std::vector<std::thread> workers_;
|
|
std::queue<HotCachePutTask> task_queue_;
|
|
size_t max_queue_capacity_;
|
|
std::mutex queue_mutex_;
|
|
std::condition_variable queue_cv_;
|
|
bool shutdown_;
|
|
};
|
|
|
|
} // namespace mooncake
|