Mooncake/mooncake-store/include/local_hot_cache.h

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