Moka

キャッシュライブラリRust高性能同期/非同期メモリキャッシュ並行処理

GitHub概要

moka-rs/moka

A high performance concurrent caching library for Rust

スター2,160
ウォッチ10
フォーク97
作成日:2020年4月5日
言語:Rust
ライセンス:Apache License 2.0

トピックス

cacheconcurrent-data-structure

スター履歴

moka-rs/moka Star History
データ取得日時: 2025/10/22 08:07

キャッシュライブラリ

Moka

概要

Mokaは、Rust言語用の高性能な並行キャッシュライブラリです。同期(sync)と非同期(future)の両方のAPIを提供し、マルチスレッド環境での安全で効率的なメモリキャッシュを実現します。Java Caffeine cacheの設計にインスパイアされた近似LFU(Least Frequently Used)エビクションポリシーや、サイズ重み付けエビクション、TTL(Time to Live)機能を組み込んでいます。

詳細

Mokaは、現代的なRust並行プログラミングのベストプラクティスに基づいて設計されたキャッシュライブラリです。sync APIは通常のスレッドベースの並行処理に最適化されており、future APIはTokioやasync-stdなどの非同期ランタイムと完全に統合されています。

主要な技術的特徴:

  • 近似LFUエビクション: アクセス頻度に基づく効率的なキー削除
  • サイズ重み付けエビクション: カスタムのweigher関数による容量制御
  • TTL/TTI機能: 時間ベースの自動期限切れ
  • エビクションリスナー: キー削除時のカスタムコールバック
  • スレッドセーフ: ArcとCloneトレイトによる軽量なクローニング

技術的優位性:

  • ロックフリー設計: 高い並行性能を実現
  • ゼロコスト抽象化: Rustの性能を最大限活用
  • 型安全性: コンパイル時の安全性チェック

メリット・デメリット

メリット

  • 高性能: Java Caffeineと同等以上の性能
  • 型安全: Rustの型システムによる安全性
  • 二重API: 同期・非同期環境の完全サポート
  • 軽量: Arc使用による効率的なメモリ使用
  • 豊富な機能: TTL、エビクションリスナー、カスタムweigher
  • 実績: Java Caffeineの実証済み設計パターン
  • 統合性: Tokio/async-stdエコシステムとの完全統合

デメリット

  • Rust限定: 他言語での利用不可
  • 学習コスト: Rustの所有権システムの理解が必要
  • 設定複雑性: 高度な機能には詳細な設定が必要
  • 分散非対応: 単一プロセス内でのキャッシュのみ
  • メモリ専用: 永続化機能なし

参考ページ

書き方の例

基本的な同期キャッシュ

use moka::sync::Cache;

fn main() {
    // 10,000エントリまで保存可能なキャッシュを作成
    let cache = Cache::new(10_000);
    
    // 値を挿入
    cache.insert("key1", "value1");
    
    // 値を取得
    if let Some(value) = cache.get(&"key1") {
        println!("Found: {}", value);
    }
    
    // キーを無効化
    cache.invalidate(&"key1");
}

非同期キャッシュ

use moka::future::Cache;

#[tokio::main]
async fn main() {
    let cache = Cache::new(10_000);
    
    // 非同期でvalueを挿入
    cache.insert("key1", "value1").await;
    
    // 非同期で値を取得
    if let Some(value) = cache.get(&"key1").await {
        println!("Found: {}", value);
    }
    
    // 非同期でキーを無効化
    cache.invalidate(&"key1").await;
}

マルチスレッド同期キャッシュ

use moka::sync::Cache;
use std::thread;

fn main() {
    let cache = Cache::new(10_000);
    
    // 複数スレッドでキャッシュを共有
    let threads: Vec<_> = (0..16)
        .map(|i| {
            let my_cache = cache.clone(); // 軽量なクローン
            thread::spawn(move || {
                let key = format!("key{}", i);
                let value = format!("value{}", i);
                
                my_cache.insert(key.clone(), value.clone());
                assert_eq!(my_cache.get(&key), Some(value));
            })
        })
        .collect();
    
    for handle in threads {
        handle.join().unwrap();
    }
}

サイズ重み付けエビクション

use moka::sync::Cache;

fn main() {
    let cache = Cache::builder()
        // 文字列の長さに基づいてサイズを計算
        .weigher(|_key, value: &String| -> u32 {
            value.len().try_into().unwrap_or(u32::MAX)
        })
        // 32MiBまでの値を保持
        .max_capacity(32 * 1024 * 1024)
        .build();
    
    cache.insert("small", "a".to_string());
    cache.insert("large", "a".repeat(1024 * 1024)); // 1MiB
}

TTL(Time to Live)設定

use moka::sync::Cache;
use std::time::Duration;

fn main() {
    let cache = Cache::builder()
        .max_capacity(10_000)
        .time_to_live(Duration::from_secs(30)) // 30秒後に期限切れ
        .time_to_idle(Duration::from_secs(5))  // 5秒間未アクセスで期限切れ
        .build();
    
    cache.insert("temporary", "data");
    
    // 30秒後または5秒間未アクセス後に自動削除
}

エビクションリスナー

use moka::sync::Cache;

fn main() {
    let cache = Cache::builder()
        .max_capacity(100)
        .eviction_listener(|key, _value, cause| {
            println!("エビクション: キー '{}', 理由: {:?}", key, cause);
        })
        .build();
    
    // キャッシュが満杯になるとエビクションリスナーが呼ばれる
    for i in 0..150 {
        cache.insert(format!("key{}", i), format!("value{}", i));
    }
}

Arc活用によるクローン最適化

use moka::sync::Cache;
use std::sync::Arc;

fn main() {
    let cache: Cache<String, Arc<Vec<u8>>> = Cache::new(1000);
    
    // 大きなデータをArcでラップ
    let large_data = Arc::new(vec![0u8; 2 * 1024 * 1024]); // 2MB
    
    cache.insert("large_key".to_string(), Arc::clone(&large_data));
    
    // get()では安価なArc::clone()のみが実行される
    if let Some(data) = cache.get(&"large_key".to_string()) {
        println!("データサイズ: {} bytes", data.len());
    }
}