キャッシュライブラリ
概要
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";
?>