Memcache の使用方法

このページでは、Google Cloud Platform Console を使用して、アプリケーション用に Memcache サービスを構成してモニタリングする方法を説明します。また、JCache インターフェースを使用して一般的なタスクを行う方法と、Memcache の低レベル App Engine API for Java を使用して同時書き込みを処理する方法についても説明します。Memcache については、Memcache の概要をご覧ください。

Memcache を構成する

  1. Google Cloud Platform Console で [Memcache] ページに移動します。
    [Memcache] ページに移動
  2. 使用する Memcache サービスレベルを選択します。

    • 共有(デフォルト) - 無料で、ベスト エフォート ベースでキャッシュ容量を提供します。
    • 専用 - キャッシュ サイズ使用量の GB 時間で請求され、アプリケーションに対して固定キャッシュ容量が独占的に割り当てられます。

    使用可能なサービスクラスについては、Memcache の概要をご覧ください。

JCache の使用

App Engine Java SDK では、Memcache にアクセスするための JCache インターフェース(JSR 107)がサポートされます。このインターフェースは javax.cache パッケージに含まれています。

JCache を使用すると、値を設定して取得し、値がキャッシュから期限切れになる方法を制御したり、キャッシュの内容を検査したり、キャッシュの統計情報を取得したりできます。また、「リスナー」を使用して、値の設定時や削除時のカスタム動作を追加することもできます。

App Engine の実装では、JCache API 標準の忠実なサブセットの実装に努めます(JCache の詳細については、JSR 107 をご覧ください)。ただし、JCache を使用する代わりに、Memcache の低レベル API を使用して、基になるサービスの多くの機能にアクセスすることを検討することもできます。

キャッシュ インスタンスの取得

javax.cache.Cache インターフェースの実装を使用して、キャッシュを操作します。CacheFactory を使用してキャッシュ インスタンスを取得します。これは CacheManager の静的メソッドから取得します。次のコードは、デフォルトの構成でキャッシュ インスタンスを取得します。

import java.util.Collections;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;

// ...
        Cache cache;
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            cache = cacheFactory.createCache(Collections.emptyMap());
        } catch (CacheException e) {
            // ...
        }

CacheFactory の createCache() メソッドでは構成プロパティのマップを受け取ります。これらのプロパティについては、後述します。デフォルト値をそのまま使用するには、メソッドに空のマップを渡します。

値の指定と取得

キャッシュはマップと同様に動作します。put() メソッドを使用してキーや値を格納し、get() メソッドを使用して値を取得します。キーまたは値には任意のシリアル化可能オブジェクトを使用できます。

        String key;      // ...
        byte[] value;    // ...

        // Put the value into the cache.
        cache.put(key, value);

        // Get the value from the cache.
        value = (byte[]) cache.get(key);

複数の値を指定するには、マップを引数として putAll() メソッドを呼び出すことができます。

(直ちに強制排除するために)キャッシュから値を削除するには、キーを引数として remove() メソッドを呼び出します。キャッシュからアプリケーションのすべての値を削除するには、clear() メソッドを呼び出します。

containsKey() メソッドではキーを受け取り、そのキーを持つ値がキャッシュ内に存在しているかどうかを示す booleantrue または false)を返します。isEmpty() メソッドはキャッシュが空かどうかをテストします。size() メソッドは、現在キャッシュ内にある値の数を返します。

有効期限の構成

デフォルトでは、メモリ不足のために強制排除されるか、アプリケーションによって明示的に削除されるか、または別の理由(停止など)のために使用不可になるまで、すべての値が可能な限りキャッシュ内に保持されます。アプリケーションで値の有効期限、値が使用可能である最大時間を指定できます。有効期限は値の設定時点からの相対的な時間数、または絶対日時として設定できます。

キャッシュ インスタンスの作成時に、構成プロパティを使用して有効期限ポリシーを指定します。そのインスタンスで指定されたすべての値で同じ有効期限ポリシーを使用します。たとえば、値が設定された 1 時間(3,600 秒)後に期限切れになるようにキャッシュ インスタンスを構成するには、次のようにします。

import java.util.HashMap;
import java.util.Map;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import javax.concurrent.TimeUnit;
import com.google.appengine.api.memcache.jsr107cache.GCacheFactory;

// ...
        Cache cache;
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            Map<Object, Object> properties = new HashMap<>();
            properties.put(GCacheFactory.EXPIRATION_DELTA, TimeUnit.HOURS.toSeconds(1));
            cache = cacheFactory.createCache(properties);
        } catch (CacheException e) {
            // ...
        }

次のプロパティで値の有効期限を制御します。

  • GCacheFactory.EXPIRATION_DELTA: 値の設定時点からの相対的な時間数(整数の秒数)を指定して、値の有効期限を設定します。
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: 値の設定時点からの相対的な時間数(整数のミリ秒数)を指定して、値の有効期限を設定します。
  • GCacheFactory.EXPIRATION: 特定の日時を java.util.Date として指定し、値の有効期限を設定します。

ポリシーの設定の構成

デフォルトでは、キャッシュに値を設定すると、指定したキーを持つ値がない場合はその値が追加され、指定したキーを持つ値がある場合は値が置換されます。値の追加のみ(既存の値を保護する)、または値の置換のみ(追加しない)を行うようにキャッシュを構成できます。

import java.util.HashMap;
import java.util.Map;
import com.google.appengine.api.memcache.MemcacheService;

// ...
        Map<Object, Object> properties = new HashMap<>();
        properties.put(MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT, true);

次のプロパティでポリシーの設定を制御します。

  • MemcacheService.SetPolicy.SET_ALWAYS: キーを持つ値が存在しない場合は値を追加し、キーを持つ値が存在する場合は既存の値を置換します。これがデフォルトです
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: キーを持つ値が存在しない場合は値を追加し、キーが存在する場合は何も行いません。
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: キーを持つ値が存在しない場合は何も行わず、キーを持つ値が存在する場合は既存の値を置換します。

キャッシュ統計の取得

アプリケーションで、それ自体のキャッシュの使用状況についての統計を取得できます。このような統計情報は、キャッシュの動作をモニタリングして調整するために役立ちます。統計情報にアクセスするには、キャッシュの getCacheStatistics() メソッドを呼び出すことによって取得する CacheStatistics オブジェクトを使用します。

取得可能な統計情報には、キャッシュ ヒットの数(存在していたキーについて取得)、キャッシュ ミスの数(存在しないキーについて取得)、キャッシュ内の値の数が含まれます。

import javax.cache.CacheStatistics;

        CacheStatistics stats = cache.getCacheStatistics();
        int hits = stats.getCacheHits();
        int misses = stats.getCacheMisses();

App Engine の実装では、ヒットとミスの数のリセットがサポートされません。ヒットとミスの数は無期限に維持されますが、Memcache サーバーの一時的な状態のためにリセットされる場合があります。

Google Cloud Platform Console での Memcache のモニタリング

  1. Google Cloud Platform Console で [Memcache] ページに移動します。
    [Memcache] ページに移動
  2. 次のレポートを調べます。
    • Memcache サービスレベル: アプリケーションは共有または専用のどちらのサービスレベルを使用しているかを示します。プロジェクトのオーナーの場合は、2 つのサービスレベルを切り替えることができます。詳細については、サービスレベルをご覧ください。
    • ヒット率: Memcache のヒットとミスのパーセンテージと実数を示します。
    • キャッシュ内のアイテム
    • 最も古いアイテムの経過期間: 最も古いキャッシュされたアイテムの経過期間。アイテムの経過時間は使用(読み取りまたは書き込み)されるたびにリセットされます。
    • 総キャッシュ サイズ
  3. 次の操作を行うことができます。

    • 新しいキー: 新しいキーをキャッシュに追加します。
    • キーを検索: 既存のキーを取得します。
    • キャッシュをフラッシュ: キャッシュからすべてのキーと値のペアを削除します。
  4. (専用 Memcache のみ)ホットキーのリストを参照します。

    • 「ホットキー」は Memcache 内の 100 クエリ/秒(QPS)以上を受信するキーです。
    • このリストには QPS の高い順で並べ換えられた最大 100 個のホットキーが含まれます。
    • キー空間全体により均等に負荷を分散する方法のヒントについては、App Engine の Memcache のベスト プラクティスの記事をお読みください。

同時書き込みの処理

他の同時書き込みリクエストを受け取る可能性がある Memcache キーの値を更新する場合、putget の代わりに低レベル Memcache メソッド putIfUntouchedgetIdentifiable を使用する必要があります。メソッド putIfUntouchedgetIdentifiable では、同時に処理される複数のリクエストを許可し、同じ Memcache キーの値をアトミックに更新できるようにすることで、競合状態を回避します。

次のコード スニペットは、他のクライアントからの同時更新リクエストを受ける可能性のあるキーの値を安全に更新する 1 つの方法を示しています。

@SuppressWarnings("serial")
public class MemcacheConcurrentServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
      ServletException {
    String path = req.getRequestURI();
    if (path.startsWith("/favicon.ico")) {
      return; // ignore the request for favicon.ico
    }

    String key = "count-concurrent";
    // Using the synchronous cache.
    MemcacheService syncCache = MemcacheServiceFactory.getMemcacheService();

    // Write this value to cache using getIdentifiable and putIfUntouched.
    for (long delayMs = 1; delayMs < 1000; delayMs *= 2) {
      IdentifiableValue oldValue = syncCache.getIdentifiable(key);
      byte[] newValue = oldValue == null
          ? BigInteger.valueOf(0).toByteArray()
              : increment((byte[]) oldValue.getValue()); // newValue depends on old value
      resp.setContentType("text/plain");
      resp.getWriter().print("Value is " + new BigInteger(newValue).intValue() + "\n");
      if (oldValue == null) {
        // Key doesn't exist. We can safely put it in cache.
        syncCache.put(key, newValue);
        break;
      } else if (syncCache.putIfUntouched(key, oldValue, newValue)) {
        // newValue has been successfully put into cache.
        break;
      } else {
        // Some other client changed the value since oldValue was retrieved.
        // Wait a while before trying again, waiting longer on successive loops.
        try {
          Thread.sleep(delayMs);
        } catch (InterruptedException e) {
          throw new ServletException("Error when sleeping", e);
        }
      }
    }
  }

  /**
   * Increments an integer stored as a byte array by one.
   * @param oldValue a byte array with the old value
   * @return         a byte array as the old value increased by one
   */
  private byte[] increment(byte[] oldValue) {
    long val = new BigInteger(oldValue).intValue();
    val++;
    return BigInteger.valueOf(val).toByteArray();
  }
}

このサンプルコードをさらに改善する方法として、再試行回数の制限を設定し、App Engine リクエストがタイムアウトするほどの長時間のブロックを防ぐことができます。

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Java の App Engine スタンダード環境