JavaによるMemcached連携の実践ガイド

Memcachedは、高速な分散型インメモリキャッシュシステムであり、Javaアプリケーションとの統合にはspymemcachedライブラリが広く採用されています。以下に、接続・操作・最適化を含む実装パターンを再構成した実践的な解説を示します。

依存関係の設定

最新安定版(v2.12.3)を用いる場合、Mavenではpom.xmlに次のように記述します:

<dependency>
  <groupId>net.spy</groupId>
  <artifactId>spymemcached</artifactId>
  <version>2.12.3</version>
</dependency>

Gradleではbuild.gradleに以下を追加します:

implementation 'net.spy:spymemcached:2.12.3'

基本的なキャッシュ操作(再設計版)

以下のクラスは、接続管理とライフサイクルを明示的に分離し、例外処理も強化しています:

import net.spy.memcached.MemcachedClient;
import net.spy.memcached.AddrUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class CacheManager {
    private final MemcachedClient client;

    public CacheManager(String serverAddress) throws IOException {
        this.client = new MemcachedClient(AddrUtil.getAddresses(serverAddress));
        // タイムアウトを明示的に設定
        client.setOpTimeout(5, TimeUnit.SECONDS);
    }

    public boolean store(String key, Object value, int ttlSeconds) {
        return client.set(key, ttlSeconds, value).isDone();
    }

    public <T> T retrieve(String key, Class<T> type) {
        Object raw = client.get(key);
        return type.isInstance(raw) ? type.cast(raw) : null;
    }

    public boolean evict(String key) {
        return client.delete(key).isDone();
    }

    public void close() {
        if (client != null) {
            client.shutdown();
        }
    }

    // 使用例
    public static void main(String[] args) {
        try (CacheManager cache = new CacheManager("127.0.0.1:11211")) {
            cache.store("user:1001", "Alice", 1800);  // 30分有効
            cache.store("user:1002", "Bob", 1800);

            String user1 = cache.retrieve("user:1001", String.class);
            System.out.println("Fetched: " + user1); // Alice

            cache.evict("user:1001");
        } catch (IOException e) {
            System.err.println("Failed to initialize cache client: " + e.getMessage());
        }
    }
}

一括操作と楽観的ロックの活用

高負荷環境では、getBulk()による複数キーの一括取得や、gets()/cas()による競合回避が重要です。以下はその応用例です:

import java.util.HashMap;
import java.util.Map;
import net.spy.memcached.CASResponse;
import net.spy.memcached.CASValue;

public class AdvancedCacheOps {
    private final MemcachedClient client;

    public AdvancedCacheOps(String endpoint) throws IOException {
        this.client = new MemcachedClient(AddrUtil.getAddresses(endpoint));
    }

    // 複数キーの一括取得(効率的なI/O)
    public Map<String, String> fetchProfiles(String... userIds) {
        Map<String, Object> bulkResult = client.getBulk(userIds);
        Map<String, String> result = new HashMap<>();
        for (Map.Entry<String, Object> entry : bulkResult.entrySet()) {
            if (entry.getValue() instanceof String) {
                result.put(entry.getKey(), (String) entry.getValue());
            }
        }
        return result;
    }

    // CASによるアトミックなカウンタ更新
    public boolean incrementCounter(String key) {
        for (int attempt = 0; attempt < 3; attempt++) {
            CASValue<Object> current = client.gets(key);
            if (current == null) {
                return client.set(key, 0, "1").isDone();
            }
            long newValue = Long.parseLong(current.getValue().toString()) + 1;
            CASResponse response = client.cas(key, current.getCas(), String.valueOf(newValue));
            if (response == CASResponse.OK) {
                return true;
            }
        }
        return false;
    }
}

この実装では、CAS失敗時のリトライロジックを組み込み、データ整合性を保証しています。また、getBulk()はネットワーク往復を最小化し、スループット向上に寄与します。

運用上の注意点

  • 接続プーリングはspymemcachedでは非対応のため、シングルトンまたはDIコンテナでMemcachedClientを共有することを推奨
  • 値のシリアライズはデフォルトでJavaのSerializableを使用するが、パフォーマンスを重視する場合はJSONやProtobufへの置き換えを検討
  • タイムアウト値はネットワーク遅延とサービスSLAに応じて調整し、過度なブロッキングを回避

タグ: memcached spymemcached Java Caching distributed-systems

9月15日 12:53 投稿