Cachelot

Cache LibraryC++LRUMemory CacheHigh PerformanceServerMemcached Compatible

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;
    }
}