Cachelot
Cache Library
Cachelot
Overview
Cachelot is a high-performance LRU cache library written in C++ and a Memcached-compatible distributed cache server designed with emphasis on memory efficiency and high speed.
Details
Cachelot is a free and open-source C++ cache library provided under the BSD 2-Clause license, offering LRU cache functionality for applications requiring high-speed operation. It operates with a fixed amount of memory and achieves very high memory utilization of 95-98% without using a garbage collector. The single-threaded design allows 99% CPU utilization without wasting CPU cycles on locks and unnecessary RAM access, and it scales up to 1024 cores. It supports a wide range of environments from resource-constrained environments like IoT devices and handheld devices to servers with large amounts of RAM. In addition to serving as a cache library, it also provides the cachelotd server compatible with Memcached, achieving 15% RAM savings and faster response times compared to Memcached.
Advantages and Disadvantages
Advantages
- Ultra High Performance: Extremely fast operation with optimized C++ implementation
- Memory Efficient: High memory utilization rate of 95-98% with low overhead of 5-7%
- Scalability: Scalable design up to 1024 cores
- Single-threaded: Stable performance without lock contention
- Cross-platform: Supports Linux, macOS, and Windows
- Memcached Compatible: Compatible with existing Memcached clients
- Lightweight: Suitable from IoT devices to large-scale servers
Disadvantages
- C++ Only: Requires language bindings for direct use from other languages
- Learning Curve: Requires knowledge of C++ and understanding of memory management
- Complexity: Implementation complexity due to single-threaded design
- New Project: Relatively new with limited track record
- Documentation: Less documentation compared to major projects
- Debugging: Debugging difficulty due to low-level implementation
Key Links
Code Examples
Basic LRU Cache Usage
#include <cachelot/cache.h>
#include <iostream>
#include <string>
int main() {
// Create 1MB cache
cachelot::Cache cache(1024 * 1024);
// Store data
std::string key = "user:123";
std::string value = "John Doe";
cache.set(key, value);
// Retrieve data
auto result = cache.get(key);
if (result) {
std::cout << "Cache hit: " << *result << std::endl;
} else {
std::cout << "Cache miss" << std::endl;
}
return 0;
}
Cache with Expiration
#include <cachelot/cache.h>
#include <chrono>
void expiration_example() {
cachelot::Cache cache(1024 * 1024);
// Store data with 30-second expiration
std::string key = "session:abc123";
std::string session_data = "user_session_data";
auto expire_time = std::chrono::steady_clock::now() +
std::chrono::seconds(30);
cache.set(key, session_data, expire_time);
// Retrieve immediately (valid)
auto result1 = cache.get(key);
if (result1) {
std::cout << "Session is valid: " << *result1 << std::endl;
}
// Will be automatically deleted after 30 seconds
}
Using Custom Keys
#include <cachelot/cache.h>
#include <vector>
// Define custom key type
struct UserKey {
int user_id;
std::string region;
bool operator==(const UserKey& other) const {
return user_id == other.user_id && region == other.region;
}
};
// Custom hash function
namespace std {
template<>
struct hash<UserKey> {
size_t operator()(const UserKey& key) const {
return hash<int>()(key.user_id) ^
hash<string>()(key.region);
}
};
}
void custom_key_example() {
cachelot::Cache cache(2 * 1024 * 1024); // 2MB
UserKey key{123, "US"};
std::string user_data = "John Doe from US";
cache.set(key, user_data);
auto result = cache.get(key);
if (result) {
std::cout << "User data: " << *result << std::endl;
}
}
Getting Statistics
#include <cachelot/cache.h>
void statistics_example() {
cachelot::Cache cache(1024 * 1024);
// Multiple data operations
for (int i = 0; i < 100; ++i) {
std::string key = "key_" + std::to_string(i);
std::string value = "value_" + std::to_string(i);
cache.set(key, value);
}
// Retrieve some keys to generate hit rate
for (int i = 0; i < 50; ++i) {
std::string key = "key_" + std::to_string(i);
auto result = cache.get(key);
}
// Generate misses with non-existent keys
for (int i = 200; i < 220; ++i) {
std::string key = "missing_key_" + std::to_string(i);
auto result = cache.get(key);
}
// Get statistics
auto stats = cache.get_stats();
std::cout << "Hits: " << stats.hits << std::endl;
std::cout << "Misses: " << stats.misses << std::endl;
std::cout << "Items: " << stats.items << std::endl;
std::cout << "Memory used: " << stats.memory_used << " bytes" << std::endl;
}
Caching Binary Data
#include <cachelot/cache.h>
#include <vector>
#include <cstring>
void binary_data_example() {
cachelot::Cache cache(1024 * 1024);
// Simulate binary data like image data
std::vector<uint8_t> image_data(1024, 0xFF); // 1KB image data
std::string image_key = "image:profile_123.jpg";
// Store binary data in cache
cache.set(image_key, image_data);
// Retrieve binary data
auto result = cache.get(image_key);
if (result) {
const auto& cached_data = *result;
std::cout << "Image data size: " << cached_data.size()
<< " bytes" << std::endl;
// Data integrity check
if (cached_data == image_data) {
std::cout << "Data integrity OK" << std::endl;
}
}
}
Multi-Type Cache
#include <cachelot/cache.h>
#include <variant>
#include <string>
// Cache value that can store multiple types
using CacheValue = std::variant<std::string, int, double, std::vector<int>>;
class MultiTypeCache {
private:
cachelot::Cache cache_;
public:
MultiTypeCache(size_t size) : cache_(size) {}
template<typename T>
void set(const std::string& key, const T& value) {
CacheValue cache_value = value;
cache_.set(key, cache_value);
}
template<typename T>
std::optional<T> get(const std::string& key) {
auto result = cache_.get(key);
if (result) {
const auto& cache_value = *result;
if (std::holds_alternative<T>(cache_value)) {
return std::get<T>(cache_value);
}
}
return std::nullopt;
}
};
void multi_type_example() {
MultiTypeCache cache(1024 * 1024);
// Store different types of data
cache.set("string_key", std::string("Hello World"));
cache.set("int_key", 42);
cache.set("double_key", 3.14159);
cache.set("vector_key", std::vector<int>{1, 2, 3, 4, 5});
// Retrieve data
auto str_val = cache.get<std::string>("string_key");
auto int_val = cache.get<int>("int_key");
auto double_val = cache.get<double>("double_key");
auto vector_val = cache.get<std::vector<int>>("vector_key");
if (str_val) std::cout << "String: " << *str_val << std::endl;
if (int_val) std::cout << "Integer: " << *int_val << std::endl;
if (double_val) std::cout << "Double: " << *double_val << std::endl;
if (vector_val) {
std::cout << "Vector: ";
for (const auto& item : *vector_val) {
std::cout << item << " ";
}
std::cout << std::endl;
}
}