データベース

Memcached

概要

Memcachedは、メモリベースの高速キーバリュー型キャッシュシステムです。データベースの負荷軽減やWebアプリケーションの応答速度向上を目的として設計されており、シンプルで軽量な実装が特徴です。

動的Webアプリケーションにおいて、データベースクエリ結果やAPIレスポンスなどの計算コストの高いデータをメモリ上にキャッシュすることで、大幅なパフォーマンス向上を実現します。

詳細

  • 開発: 2003年にBrad Fitzpatrickによって開発
  • アーキテクチャ: マルチスレッド対応、イベント駆動型サーバー
  • データ保存: メモリのみ(永続化なし)
  • プロトコル: シンプルなテキストベースプロトコル
  • 分散: サーバー側での分散機能なし(クライアント側でハッシュ分散)
  • レプリケーション: なし(単純なキャッシュ用途に特化)
  • プロキシ機能: 1.6以降でビルトインプロキシをサポート
  • SASL認証: セキュリティ機能をサポート
  • LRU: メモリ不足時の自動データ削除
  • TTL: 自動期限切れ機能

メリット・デメリット

メリット

  • 高速性: メモリベースによる非常に高い読み書き速度
  • シンプル: 理解しやすい仕組みと軽量な実装
  • 安定性: 長期間にわたる実績と安定した動作
  • スケーラビリティ: 水平スケールが容易
  • 多言語サポート: 豊富なクライアントライブラリ
  • 低レイテンシ: ミリ秒未満の応答時間

デメリット

  • 永続化なし: サーバー再起動でデータが失われる
  • 単純な構造: 複雑なデータ構造は扱えない
  • レプリケーションなし: 高可用性は外部で実装が必要
  • メモリ制限: 使用可能メモリに依存
  • 原子操作の制限: 複雑なトランザクションは不可能

主要リンク

書き方の例

セットアップ・設定

# Ubuntu/Debianでのインストール
sudo apt-get install memcached

# CentOS/RHELでのインストール
sudo yum install memcached

# サーバー起動(基本設定)
memcached -m 64 -p 11211 -u memcache -d

# 詳細設定での起動
memcached -m 512 -p 11211 -l 127.0.0.1 -d -v

基本操作(CRUD)

# Python(python-memcached)での基本操作
import memcache

# 接続設定
mc = memcache.Client(['127.0.0.1:11211'], debug=0)

# データの保存(SET)
mc.set("user:1001", {"name": "田中", "age": 30}, time=3600)
mc.set("counter", 100, time=300)

# データの取得(GET)
user_data = mc.get("user:1001")
counter = mc.get("counter")

# データの更新
mc.set("user:1001", {"name": "田中", "age": 31}, time=3600)

# データの削除
mc.delete("user:1001")

高度な操作

# 複数キーの一括操作
keys = ["user:1001", "user:1002", "user:1003"]
users = mc.get_multi(keys)

# 原子的な増減操作
mc.incr("page_views", delta=1)
mc.decr("remaining_count", delta=5)

# 条件付き保存(キーが存在しない場合のみ)
success = mc.add("lock:resource:123", "locked", time=30)

# 置換操作(キーが存在する場合のみ)
mc.replace("config:cache_timeout", 600, time=7200)

プロキシ機能(Memcached 1.6以降)

-- プロキシ設定ファイル(config.lua)
pools{
    main = {
        backends = {
            "127.0.0.1:11214",
            "127.0.0.1:11215",
        }
    }
}

routes{
    default = route_direct{ child = "main" }
}
# プロキシモードでの起動
memcached -o proxy_config=routelib.lua,proxy_arg=config.lua -p 11212 &

実用例

// Node.js での実装例
const memcached = require('memcached');
const mc = new memcached('127.0.0.1:11211');

// キャッシュ戦略の実装
async function getUserProfile(userId) {
    const cacheKey = `user_profile:${userId}`;
    
    // キャッシュから取得を試行
    return new Promise((resolve, reject) => {
        mc.get(cacheKey, async (err, data) => {
            if (err) return reject(err);
            
            if (data) {
                // キャッシュヒット
                console.log('Cache hit');
                resolve(data);
            } else {
                // キャッシュミス:データベースから取得
                console.log('Cache miss');
                const profileData = await fetchUserFromDatabase(userId);
                
                // 1時間キャッシュに保存
                mc.set(cacheKey, profileData, 3600, (setErr) => {
                    if (setErr) console.error('Cache set error:', setErr);
                });
                
                resolve(profileData);
            }
        });
    });
}

ベストプラクティス

# 接続プールと例外処理
import memcache
import json
import logging

class MemcachedManager:
    def __init__(self, servers=['127.0.0.1:11211']):
        self.mc = memcache.Client(servers, debug=0)
        self.logger = logging.getLogger(__name__)
    
    def safe_get(self, key, default=None):
        try:
            return self.mc.get(key) or default
        except Exception as e:
            self.logger.error(f"Memcached get error: {e}")
            return default
    
    def safe_set(self, key, value, ttl=3600):
        try:
            return self.mc.set(key, value, time=ttl)
        except Exception as e:
            self.logger.error(f"Memcached set error: {e}")
            return False
    
    def cache_with_fallback(self, key, fetch_func, ttl=3600):
        # キャッシュから取得
        data = self.safe_get(key)
        if data is not None:
            return data
        
        # フォールバック:元データを取得
        try:
            fresh_data = fetch_func()
            self.safe_set(key, fresh_data, ttl)
            return fresh_data
        except Exception as e:
            self.logger.error(f"Fallback fetch error: {e}")
            raise

CLI操作

# 統計情報の確認
echo "stats" | nc 127.0.0.1 11211

# アイテム数の確認
echo "stats items" | nc 127.0.0.1 11211

# サーバー情報の確認
echo "stats settings" | nc 127.0.0.1 11211

# 特定のキーの削除
echo "delete user:1001" | nc 127.0.0.1 11211

# キャッシュのフラッシュ(全削除)
echo "flush_all" | nc 127.0.0.1 11211