Cachelot

キャッシュライブラリC++LRUメモリキャッシュ高性能サーバーMemcached互換

キャッシュライブラリ

Cachelot

概要

CachelotはC++で書かれた高性能なLRUキャッシュライブラリおよびMemcached互換の分散キャッシュサーバーで、メモリ効率性と高速性を重視した設計になっています。

詳細

CachelotはBSD 2-Clauseライセンスで提供される無料のオープンソースC++キャッシュライブラリで、高速動作が要求されるアプリケーション向けのLRUキャッシュ機能を提供します。固定メモリ量での動作を前提とし、ガベージコレクタを使わずに95-98%という非常に高いメモリ利用率を実現します。シングルスレッド設計により、ロックや無駄なRAMアクセスでCPUサイクルを浪費することなく、CPUの99%を有効活用できます。また、1024コアまでのスケーラビリティを持ちます。IoTデバイスやハンドヘルドデバイスのようなリソース制限環境から、大容量RAMを持つサーバーまで幅広く対応可能です。キャッシュライブラリとしてだけでなく、Memcached互換のcachelotdサーバーも提供し、Memcachedよりも15%のRAM節約と高速なレスポンスを実現します。

メリット・デメリット

メリット

  • 超高性能: 最適化されたC++実装による極めて高速な動作
  • メモリ効率: 95-98%の高いメモリ利用率と5-7%の低いオーバーヘッド
  • スケーラビリティ: 1024コアまでのスケーラブル設計
  • シングルスレッド: ロック競合なしで安定したパフォーマンス
  • クロスプラットフォーム: Linux、macOS、Windows対応
  • Memcached互換: 既存のMemcachedクライアントと互換性
  • 軽量: IoTデバイスから大規模サーバーまで対応

デメリット

  • C++専用: 他の言語からの直接利用には言語バインディングが必要
  • 学習コスト: C++の知識とメモリ管理の理解が必要
  • 複雑性: シングルスレッド設計による実装の複雑さ
  • 新しいプロジェクト: 比較的新しく、実績が限定的
  • ドキュメント: 大手プロジェクトと比較してドキュメントが少ない
  • デバッグ: 低レベル実装によるデバッグの難しさ

主要リンク

書き方の例

基本的なLRUキャッシュ使用

#include <cachelot/cache.h>
#include <iostream>
#include <string>

int main() {
    // 1MB のキャッシュを作成
    cachelot::Cache cache(1024 * 1024);
    
    // データの保存
    std::string key = "user:123";
    std::string value = "John Doe";
    cache.set(key, value);
    
    // データの取得
    auto result = cache.get(key);
    if (result) {
        std::cout << "キャッシュヒット: " << *result << std::endl;
    } else {
        std::cout << "キャッシュミス" << std::endl;
    }
    
    return 0;
}

有効期限付きキャッシュ

#include <cachelot/cache.h>
#include <chrono>

void expiration_example() {
    cachelot::Cache cache(1024 * 1024);
    
    // 30秒の有効期限でデータを保存
    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);
    
    // 即座に取得(有効)
    auto result1 = cache.get(key);
    if (result1) {
        std::cout << "セッションが有効: " << *result1 << std::endl;
    }
    
    // 30秒後には自動的に削除される
}

カスタムキーの使用

#include <cachelot/cache.h>
#include <vector>

// カスタムキー型の定義
struct UserKey {
    int user_id;
    std::string region;
    
    bool operator==(const UserKey& other) const {
        return user_id == other.user_id && region == other.region;
    }
};

// カスタムハッシュ関数
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 << "ユーザーデータ: " << *result << std::endl;
    }
}

統計情報の取得

#include <cachelot/cache.h>

void statistics_example() {
    cachelot::Cache cache(1024 * 1024);
    
    // 複数のデータ操作
    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);
    }
    
    // いくつかのキーを取得してヒット率を生成
    for (int i = 0; i < 50; ++i) {
        std::string key = "key_" + std::to_string(i);
        auto result = cache.get(key);
    }
    
    // 存在しないキーでミスを生成
    for (int i = 200; i < 220; ++i) {
        std::string key = "missing_key_" + std::to_string(i);
        auto result = cache.get(key);
    }
    
    // 統計情報を取得
    auto stats = cache.get_stats();
    std::cout << "ヒット数: " << stats.hits << std::endl;
    std::cout << "ミス数: " << stats.misses << std::endl;
    std::cout << "アイテム数: " << stats.items << std::endl;
    std::cout << "使用メモリ: " << stats.memory_used << " bytes" << std::endl;
}

バイナリデータのキャッシュ

#include <cachelot/cache.h>
#include <vector>
#include <cstring>

void binary_data_example() {
    cachelot::Cache cache(1024 * 1024);
    
    // 画像データなどのバイナリデータをシミュレート
    std::vector<uint8_t> image_data(1024, 0xFF);  // 1KB の画像データ
    
    std::string image_key = "image:profile_123.jpg";
    
    // バイナリデータをキャッシュに保存
    cache.set(image_key, image_data);
    
    // バイナリデータを取得
    auto result = cache.get(image_key);
    if (result) {
        const auto& cached_data = *result;
        std::cout << "画像データサイズ: " << cached_data.size() 
                  << " bytes" << std::endl;
        
        // データの整合性チェック
        if (cached_data == image_data) {
            std::cout << "データの整合性OK" << std::endl;
        }
    }
}

マルチタイプキャッシュ

#include <cachelot/cache.h>
#include <variant>
#include <string>

// 複数の型を格納できるキャッシュ値
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);
    
    // 異なる型のデータを保存
    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});
    
    // データを取得
    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 << "文字列: " << *str_val << std::endl;
    if (int_val) std::cout << "整数: " << *int_val << std::endl;
    if (double_val) std::cout << "実数: " << *double_val << std::endl;
    if (vector_val) {
        std::cout << "ベクター: ";
        for (const auto& item : *vector_val) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
}