インメモリデータベース

Memcached

概要

Memcachedは、高パフォーマンスな分散メモリオブジェクトキャッシングシステムです。シンプルなキーバリュー操作に特化し、Webアプリケーションの高速化に幅広く利用されています。軽量で効率的な設計とマルチスレッドアーキテクチャにより、ミリ秒以下のレスポンス時間でキャッシュデータへのアクセスを提供します。

詳細

Memcachedは2003年にLiveJournalで開発されたオープンソースのキャッシュシステムで、データベースやファイルシステムへのアクセスを減らしてWebアプリケーションのパフォーマンスを向上させることを目的としています。

キーバリューペアでデータを管理し、ハッシュテーブルを使用して固定サイズのバケットに連結リストを組み合わせた効率的なデータ構造を採用しています。1つの値に対して1MBのサイズ制限がありますが、このシンプルなモデルが高パフォーマンスを実現しています。

2025年現在、Memcachedはビルトインプロキシ機能や高度なルーティング機能を提供し、複数のバックエンドサーバーの管理やフェイルオーバー機能など、エンタープライズグレードの機能も提供しています。

メリット・デメリット

メリット

  • 超高速アクセス: ミリ秒以下のレスポンス時間でデータ取得
  • シンプルな設計: キーバリュー操作のみに特化したシンプルなアーキテクチャ
  • 高いスケーラビリティ: マルチスレッドで複数CPUコアを効率利用
  • 軽量設計: メモリ使用量が少なく、高効率
  • 幅広いクライアントサポート: PHP、Python、Javaなど多数言語対応
  • 成熟したエコシステム: 20年以上の実績と安定性
  • 柔軟なデプロイメント: スタンドアロン、分散環境、クラウド対応

デメリット

  • 永続化非対応: メモリのみのストレージでサーバー再起動時にデータ消失
  • 機能のシンプルさ: 単純なキーバリュー操作のみで複雑なデータ操作不可
  • サイズ制限: 1値あたり1MBのサイズ制限
  • クラスタリング機能なし: ネイティブな高可用性機能なし
  • セキュリティ機能限定: 認証・暗号化などの組み込みセキュリティ機能なし

参考ページ

書き方の例

サーバー起動と基本設定

# バシックなサーバー起動
memcached -p 11211 -m 64 -d

# 複数ポートでのインスタンス起動
memcached -p 11212 -m 128 -d
memcached -p 11213 -m 128 -d

# 詳細な設定での起動
memcached -l 127.0.0.1 -p 11211 -m 64 -c 1024 -t 4 -d

# ステータス確認
echo "stats" | nc localhost 11211

PHPクライアントの使用

<?php
// Memcacheクライアントの初期化
$memcache = new Memcache();

// サーバー追加
$MEMCACHE_SERVERS = [
    "10.1.1.1", // web1
    "10.1.1.2", // web2
    "10.1.1.3", // web3
];

foreach($MEMCACHE_SERVERS as $server) {
    $memcache->addServer($server, 11211);
}

// データの保存と取得
$key = "user:123:profile";
$data = [
    "name" => "John Doe",
    "email" => "[email protected]",
    "last_login" => time()
];

// キャッシュに保存 (TTL: 3600秒)
$memcache->set($key, $data, 0, 3600);

// キャッシュから取得
$cached_data = $memcache->get($key);
if ($cached_data !== false) {
    echo "Cache hit: " . print_r($cached_data, true);
} else {
    echo "Cache miss";
}

// キャッシュ削除
$memcache->delete($key);
?>

Pythonクライアントの使用

import memcache
import json
from datetime import datetime, timedelta

# Memcachedクライアント初期化
mc = memcache.Client([
    '127.0.0.1:11211',
    '127.0.0.1:11212',
    '127.0.0.1:11213'
], debug=0)

# キャッシュデコレーター
def cache_result(expiry_time=3600):
    def decorator(func):
        def wrapper(*args, **kwargs):
            # キー生成
            cache_key = f"{func.__name__}:{hash(str(args) + str(kwargs))}"
            
            # キャッシュから取得試行
            result = mc.get(cache_key)
            if result is not None:
                return json.loads(result)
            
            # キャッシュミスの場合、関数実行
            result = func(*args, **kwargs)
            
            # 結果をキャッシュに保存
            mc.set(cache_key, json.dumps(result), time=expiry_time)
            return result
        return wrapper
    return decorator

# 使用例
@cache_result(expiry_time=1800)
def expensive_database_query(user_id):
    # 重いデータベースクエリのシミュレーション
    import time
    time.sleep(0.5)  # データベースアクセスのシミュレーション
    return {
        "user_id": user_id,
        "name": f"User {user_id}",
        "timestamp": datetime.now().isoformat()
    }

# キャッシュ効果を確認
print(expensive_database_query(123))  # 初回は遅い
print(expensive_database_query(123))  # 2回目は高速

Node.jsクライアントの使用

const memcached = require('memcached');

// Memcachedクライアント初期化
const mc = new memcached([
    '127.0.0.1:11211',
    '127.0.0.1:11212',
    '127.0.0.1:11213'
], {
    maxKeySize: 250,
    maxExpiration: 2592000,
    maxValue: 1048576,
    poolSize: 10,
    algorithm: 'md5',
    reconnect: 18000,
    timeout: 5000,
    retries: 5,
    failures: 5,
    retry: 30000,
    remove: true,
    failOverServers: ['127.0.0.1:11214']
});

// Promiseラッパー関数
const memcachedGet = (key) => {
    return new Promise((resolve, reject) => {
        mc.get(key, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
};

const memcachedSet = (key, value, ttl) => {
    return new Promise((resolve, reject) => {
        mc.set(key, value, ttl, (err) => {
            if (err) reject(err);
            else resolve(true);
        });
    });
};

// キャッシュミドルウェア関数
async function cacheMiddleware(req, res, next) {
    const cacheKey = `api:${req.originalUrl || req.url}`;
    
    try {
        const cachedResponse = await memcachedGet(cacheKey);
        if (cachedResponse) {
            return res.json(JSON.parse(cachedResponse));
        }
        
        // レスポンスをキャッシュするためにオリジナルres.jsonをラップ
        const originalJson = res.json;
        res.json = function(data) {
            memcachedSet(cacheKey, JSON.stringify(data), 300); // 5分キャッシュ
            originalJson.call(this, data);
        };
        
        next();
    } catch (err) {
        console.error('Memcached error:', err);
        next();
    }
}

// Expressアプリでの使用例
app.get('/api/users/:id', cacheMiddleware, async (req, res) => {
    const user = await getUserFromDatabase(req.params.id);
    res.json(user);
});

プロキシ設定(Luaスクリプト)

-- memcached-proxy.lua
-- プロキシ設定ファイル

-- バックエンドサーバープール定義
pools {
    main_pool = {
        backend_options = { connecttimeout = 5 },
        backends = {
            "192.168.1.10:11211",
            "192.168.1.11:11211",
            "192.168.1.12:11211"
        }
    },
    
    hot_keys_pool = {
        backends = {
            { host = "192.168.1.20", port = 11211, connecttimeout = 3 },
            { host = "192.168.1.21", port = 11211, connecttimeout = 3 },
            { host = "192.168.1.22", port = 11211, connecttimeout = 3 }
        }
    }
}

-- ルーティング設定
routes {
    map = {
        -- "hot/"で始まるキーは特別なプールへ
        hot = route_allfastest {
            children = "hot_keys_pool"
        }
    },
    
    -- デフォルトルーティング
    default = route_direct {
        child = "main_pool"
    }
}

-- グローバル設定
settings {
    backend_connect_timeout = 3,
    active_req_limit = 5
}

監視とパフォーマンスチューニング

# 統計情報の取得
echo "stats" | nc localhost 11211
echo "stats slabs" | nc localhost 11211
echo "stats items" | nc localhost 11211

# パフォーマンステストスクリプト
#!/bin/bash
# mc_conn_tester.plを使用したパフォーマンステスト
./mc_conn_tester.pl -s memcached-host -p 11211 -c 1000 --timeout 1

# ネットワークチューニング例
# /etc/sysctl.conf
net.ipv4.ip_local_port_range = 16384 65534
net.ipv4.tcp_max_tw_buckets = 262144
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1

コマンドラインツール

# telnetでの手動テスト
telnet localhost 11211
# 以下を入力:
set test_key 0 3600 5
hello
get test_key
delete test_key
quit

# スクリプトでの一括テスト
echo -e "set test 0 300 5\r\nhello\r\nget test\r\nquit\r\n" | nc localhost 11211

# メモリ使用量監視
watch -n 1 'echo "stats" | nc localhost 11211 | grep bytes'