Pocket

キャッシュライブラリ軽量インメモリ高速埋め込み型ローカルキャッシュ

キャッシュライブラリ

Pocket

概要

Pocketは、軽量でシンプルなインメモリキャッシュライブラリです。組み込み型のローカルキャッシュとして設計されており、外部依存なしで高速なデータアクセスを提供します。小〜中規模のアプリケーションや、単一プロセス内での一時的なデータ保存に最適化されており、最小限の設定で即座に利用開始できます。

詳細

Pocketは、複雑な設定や外部サーバーを必要とせず、アプリケーション内で直接動作するキャッシュライブラリです。メモリ効率と実行速度を重視した設計により、HTTPリクエスト間でのデータ保持や、計算結果の一時保存などの用途に適しています。

主要な技術的特徴:

  • 軽量設計: 最小限のメモリフットプリント
  • 高速アクセス: 直接メモリアクセスによる低レイテンシ
  • シンプルAPI: 学習コストの低い直感的なインターフェース
  • TTL対応: 時間ベースの自動期限切れ
  • 埋め込み型: 外部サーバーや複雑な設定不要
  • プロセス内: 単一アプリケーション内でのデータ共有

技術アーキテクチャ:

  • ハッシュテーブル: O(1)の高速キーアクセス
  • メモリ管理: 自動的なガベージコレクション対応
  • スレッドセーフ: 必要に応じた同期制御
  • エビクション: LRUやサイズベースの削除ポリシー

メリット・デメリット

メリット

  • 即座に利用可能: インストールと同時に使用開始
  • 軽量: 最小限のリソース消費
  • 高速: 直接メモリアクセスによる最高速度
  • シンプル: 複雑な設定や管理不要
  • 依存関係なし: 外部サーバーやサービス不要
  • 統合容易: 既存アプリケーションへの組み込みが簡単
  • デバッグ容易: 内部状態の直接確認可能

デメリット

  • プロセス限定: 単一プロセス内でのみ利用可能
  • 永続化なし: プロセス終了時にデータ消失
  • 分散対応なし: 複数サーバー間での共有不可
  • 機能限定: 高度なキャッシュ機能は提供されない場合がある
  • スケーラビリティ: 大規模データには不向き
  • メモリ制約: 物理メモリの制限を受ける

参考ページ

書き方の例

基本的なキャッシュ利用

// JavaScript/Node.js での一般的なPocket風実装例
class PocketCache {
    constructor(options = {}) {
        this.cache = new Map();
        this.ttl = options.ttl || 3600000; // デフォルト1時間
        this.maxSize = options.maxSize || 1000;
    }
    
    set(key, value, ttl = this.ttl) {
        // サイズ制限チェック
        if (this.cache.size >= this.maxSize) {
            this.evictOldest();
        }
        
        const expiresAt = Date.now() + ttl;
        this.cache.set(key, {
            value: value,
            expiresAt: expiresAt,
            accessedAt: Date.now()
        });
    }
    
    get(key) {
        const item = this.cache.get(key);
        if (!item) return null;
        
        // 期限切れチェック
        if (Date.now() > item.expiresAt) {
            this.cache.delete(key);
            return null;
        }
        
        // アクセス時刻更新(LRU用)
        item.accessedAt = Date.now();
        return item.value;
    }
    
    has(key) {
        return this.get(key) !== null;
    }
    
    delete(key) {
        return this.cache.delete(key);
    }
    
    clear() {
        this.cache.clear();
    }
    
    size() {
        return this.cache.size;
    }
    
    evictOldest() {
        let oldestKey = null;
        let oldestTime = Date.now();
        
        for (const [key, item] of this.cache) {
            if (item.accessedAt < oldestTime) {
                oldestTime = item.accessedAt;
                oldestKey = key;
            }
        }
        
        if (oldestKey) {
            this.cache.delete(oldestKey);
        }
    }
}

// 使用例
const cache = new PocketCache({ maxSize: 100, ttl: 300000 }); // 5分TTL

cache.set('user:1000', { name: 'John Doe', email: '[email protected]' });
cache.set('product:500', { name: 'Laptop', price: 999 });

const user = cache.get('user:1000');
console.log('ユーザー:', user.name);

Python実装例

import time
import threading
from collections import OrderedDict

class PocketCache:
    def __init__(self, max_size=1000, default_ttl=3600):
        self.cache = OrderedDict()
        self.max_size = max_size
        self.default_ttl = default_ttl
        self.lock = threading.RLock()
    
    def set(self, key, value, ttl=None):
        ttl = ttl or self.default_ttl
        expires_at = time.time() + ttl
        
        with self.lock:
            # サイズ制限処理
            if len(self.cache) >= self.max_size:
                self.cache.popitem(last=False)  # FIFO削除
            
            self.cache[key] = {
                'value': value,
                'expires_at': expires_at,
                'created_at': time.time()
            }
    
    def get(self, key):
        with self.lock:
            if key not in self.cache:
                return None
            
            item = self.cache[key]
            
            # 期限切れチェック
            if time.time() > item['expires_at']:
                del self.cache[key]
                return None
            
            # LRU: アクセス順序更新
            self.cache.move_to_end(key)
            return item['value']
    
    def has(self, key):
        return self.get(key) is not None
    
    def delete(self, key):
        with self.lock:
            return self.cache.pop(key, None) is not None
    
    def clear(self):
        with self.lock:
            self.cache.clear()
    
    def size(self):
        return len(self.cache)
    
    def cleanup_expired(self):
        """期限切れアイテムのクリーンアップ"""
        current_time = time.time()
        with self.lock:
            expired_keys = [
                key for key, item in self.cache.items() 
                if current_time > item['expires_at']
            ]
            for key in expired_keys:
                del self.cache[key]
        return len(expired_keys)

# 使用例
cache = PocketCache(max_size=500, default_ttl=600)  # 10分TTL

# データ保存
cache.set('session:abc123', {
    'user_id': 1000,
    'username': 'john_doe',
    'login_time': time.time()
})

cache.set('api_response:users', [
    {'id': 1, 'name': 'Alice'},
    {'id': 2, 'name': 'Bob'}
], ttl=300)  # 5分TTL

# データ取得
session = cache.get('session:abc123')
if session:
    print(f"ユーザー: {session['username']}")

users = cache.get('api_response:users')
if users:
    print(f"ユーザー数: {len(users)}")

Java実装例

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class PocketCache<K, V> {
    private static class CacheItem<V> {
        final V value;
        final long expiresAt;
        volatile long lastAccessed;
        
        CacheItem(V value, long ttlMs) {
            this.value = value;
            this.expiresAt = System.currentTimeMillis() + ttlMs;
            this.lastAccessed = System.currentTimeMillis();
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() > expiresAt;
        }
    }
    
    private final ConcurrentHashMap<K, CacheItem<V>> cache;
    private final int maxSize;
    private final long defaultTtl;
    private final ScheduledExecutorService cleanup;
    
    public PocketCache(int maxSize, long defaultTtlMs) {
        this.cache = new ConcurrentHashMap<>();
        this.maxSize = maxSize;
        this.defaultTtl = defaultTtlMs;
        
        // 定期的なクリーンアップ
        this.cleanup = Executors.newSingleThreadScheduledExecutor();
        this.cleanup.scheduleAtFixedRate(this::cleanupExpired, 
                                        60, 60, TimeUnit.SECONDS);
    }
    
    public void put(K key, V value) {
        put(key, value, defaultTtl);
    }
    
    public void put(K key, V value, long ttlMs) {
        // サイズ制限チェック
        if (cache.size() >= maxSize) {
            evictLRU();
        }
        
        cache.put(key, new CacheItem<>(value, ttlMs));
    }
    
    public V get(K key) {
        CacheItem<V> item = cache.get(key);
        if (item == null || item.isExpired()) {
            cache.remove(key);
            return null;
        }
        
        item.lastAccessed = System.currentTimeMillis();
        return item.value;
    }
    
    public boolean containsKey(K key) {
        return get(key) != null;
    }
    
    public boolean remove(K key) {
        return cache.remove(key) != null;
    }
    
    public void clear() {
        cache.clear();
    }
    
    public int size() {
        return cache.size();
    }
    
    private void evictLRU() {
        K lruKey = null;
        long oldestAccess = Long.MAX_VALUE;
        
        for (var entry : cache.entrySet()) {
            long lastAccessed = entry.getValue().lastAccessed;
            if (lastAccessed < oldestAccess) {
                oldestAccess = lastAccessed;
                lruKey = entry.getKey();
            }
        }
        
        if (lruKey != null) {
            cache.remove(lruKey);
        }
    }
    
    private void cleanupExpired() {
        cache.entrySet().removeIf(entry -> entry.getValue().isExpired());
    }
    
    public void shutdown() {
        cleanup.shutdown();
    }
}

// 使用例
public class CacheExample {
    public static void main(String[] args) {
        PocketCache<String, Object> cache = new PocketCache<>(1000, 300000); // 5分TTL
        
        // ユーザー情報をキャッシュ
        cache.put("user:1000", Map.of(
            "name", "John Doe",
            "email", "[email protected]",
            "role", "admin"
        ));
        
        // API レスポンスをキャッシュ(短いTTL)
        cache.put("api:products", Arrays.asList(
            Map.of("id", 1, "name", "Laptop"),
            Map.of("id", 2, "name", "Mouse")
        ), 60000); // 1分TTL
        
        // データ取得
        Object user = cache.get("user:1000");
        if (user != null) {
            System.out.println("ユーザー情報: " + user);
        }
        
        Object products = cache.get("api:products");
        if (products != null) {
            System.out.println("商品情報: " + products);
        }
        
        // 終了処理
        cache.shutdown();
    }
}

PHP実装例

<?php
class PocketCache {
    private $cache = [];
    private $maxSize;
    private $defaultTtl;
    
    public function __construct($maxSize = 1000, $defaultTtl = 3600) {
        $this->maxSize = $maxSize;
        $this->defaultTtl = $defaultTtl;
    }
    
    public function set($key, $value, $ttl = null) {
        $ttl = $ttl ?: $this->defaultTtl;
        $expiresAt = time() + $ttl;
        
        // サイズ制限チェック
        if (count($this->cache) >= $this->maxSize) {
            $this->evictOldest();
        }
        
        $this->cache[$key] = [
            'value' => $value,
            'expires_at' => $expiresAt,
            'accessed_at' => time()
        ];
    }
    
    public function get($key) {
        if (!isset($this->cache[$key])) {
            return null;
        }
        
        $item = $this->cache[$key];
        
        // 期限切れチェック
        if (time() > $item['expires_at']) {
            unset($this->cache[$key]);
            return null;
        }
        
        // アクセス時刻更新
        $this->cache[$key]['accessed_at'] = time();
        return $item['value'];
    }
    
    public function has($key) {
        return $this->get($key) !== null;
    }
    
    public function delete($key) {
        if (isset($this->cache[$key])) {
            unset($this->cache[$key]);
            return true;
        }
        return false;
    }
    
    public function clear() {
        $this->cache = [];
    }
    
    public function size() {
        return count($this->cache);
    }
    
    private function evictOldest() {
        $oldestKey = null;
        $oldestTime = time();
        
        foreach ($this->cache as $key => $item) {
            if ($item['accessed_at'] < $oldestTime) {
                $oldestTime = $item['accessed_at'];
                $oldestKey = $key;
            }
        }
        
        if ($oldestKey) {
            unset($this->cache[$oldestKey]);
        }
    }
    
    public function cleanupExpired() {
        $currentTime = time();
        $expiredKeys = [];
        
        foreach ($this->cache as $key => $item) {
            if ($currentTime > $item['expires_at']) {
                $expiredKeys[] = $key;
            }
        }
        
        foreach ($expiredKeys as $key) {
            unset($this->cache[$key]);
        }
        
        return count($expiredKeys);
    }
}

// 使用例
$cache = new PocketCache(500, 600); // 最大500アイテム、10分TTL

// データベースクエリ結果をキャッシュ
$cache->set('popular_products', [
    ['id' => 1, 'name' => 'ノートPC', 'views' => 1500],
    ['id' => 2, 'name' => 'マウス', 'views' => 800],
    ['id' => 3, 'name' => 'キーボード', 'views' => 600]
], 300); // 5分TTL

// ユーザー設定をキャッシュ
$cache->set('user_settings:1000', [
    'theme' => 'dark',
    'language' => 'ja',
    'notifications' => true
]);

// キャッシュから取得
$products = $cache->get('popular_products');
if ($products) {
    echo "人気商品数: " . count($products) . "\n";
}

$settings = $cache->get('user_settings:1000');
if ($settings) {
    echo "ユーザーテーマ: " . $settings['theme'] . "\n";
}

// 統計情報
echo "キャッシュサイズ: " . $cache->size() . "\n";
$expired = $cache->cleanupExpired();
echo "削除された期限切れアイテム: " . $expired . "\n";
?>