Memorystore を使用したデータのキャッシュ

高パフォーマンスでスケーラブルなウェブ アプリケーションでは多くの場合、一部のタスクの処理に、堅牢で永続的なストレージに優先して(あるいはその代わりに)分散型のインメモリ データ キャッシュを使用します。Memorystore for Redis をキャッシュ サービスとして使用することをおすすめします。Memorystore for Redis に無料枠はありません。詳細は、Memorystore の料金をご覧ください。

使用を開始する前に、アプリが Memorystore for Redis の割り当ての範囲内にあることを確認してください。

メモリ キャッシュの用途

セッション データ、ユーザー設定、およびウェブページへのクエリによって返されたその他のデータは、キャッシュに適した候補です。通常、頻繁に実行されるクエリから返された結果が、アプリですぐに表示する必要がない場合は、結果をキャッシュできます。後続のリクエストでは、キャッシュをチェックして、結果が存在しないか期限切れになった場合にのみデータベースへのクエリを実行します。

永続ストレージにバックアップせずに Memorystore にのみ値を保存する場合は、値が期限切れになり、キャッシュから削除されたときに、アプリケーションが適切に動作することを確認してください。たとえば、ユーザーのセッション データが突然なくなったために、セッションが誤動作する場合、そのデータはおそらく Memorystore だけでなく、データベースにも保存する必要があると考えられます。

Memorystore の権限について確認する

Google Cloud サービスでのすべての操作について承認を得る必要があります。たとえば、Memorystore でホストされている Redis データベースを操作するには、アプリは Memorystore へのアクセス権を持つアカウントの認証情報を提供する必要があります。

デフォルトでは、App Engine のデフォルトのサービス アカウントの認証情報が提供されます。このアカウントには、アプリと同じプロジェクトのデータベースにアクセスする権限が付与されます。

次のいずれかの条件に当てはまる場合は、認証情報を明示的に提示する別の認証方法を使用する必要があります。

  • アプリと Memorystore データベースが異なる Google Cloud プロジェクトに属する。

  • デフォルトの App Engine サービス アカウントに割り当てられているロールが変更されている。

代替認証の方法については、サーバー間の本番環境アプリケーションの認証の設定をご覧ください。

Memorystore の使用方法の概要

アプリで Memorystore を使用するには:

  1. Redis 用 Memorystore をセットアップします。そのためには、Memorystore で Redis インスタンスを作成し、アプリが Redis インスタンスとの通信に使用するサーバーレス VPC アクセスを作成する必要があります。この 2 つの独立したエンティティを作成する順序は厳密ではなく、どのような順序でも設定が可能です。このガイドでは、まずサーバーレス VPC アクセスを設定する手順について説明します。

  2. Redis のクライアント ライブラリをインストールし、Redis コマンドを使用してデータをキャッシュに保存します。

    Memorystore for Redis は Redis 用クライアント ライブラリと互換性があります。このガイドでは、Jedis クライアント ライブラリを使用してアプリから Redis コマンドを送信する方法について説明します。Jedis の使い方の詳細については、Jedis wiki をご覧ください。

  3. 更新をテストする

  4. App Engine にアプリをデプロイする

Memorystore for Redis のセットアップ

Memorystore for Redis をセットアップするには:

  1. App Engine を VPC ネットワークに接続します。アプリは VPC コネクタを介してのみ Memorystore と通信できます。

    コネクタを使用するようにアプリを構成するの説明に沿って、app.yaml ファイルに VPC 接続情報を追加します。

  2. 作成する Redis インスタンスの IP アドレスとポート番号をメモします。この情報は、コードで Redis クライアントを作成するときに使用します。

  3. Memorystore で Redis インスタンスを作成します。

    Redis インスタンスのリージョンを選択するように求められたら、App Engine アプリが配置されているのと同じリージョンを選択します。

依存関係のインストール

アプリが App Engine で実行されているときに、Jedis クライアント ライブラリをアプリで使用できるようにするには、ライブラリをアプリの依存関係に追加します。たとえば、Maven を使用する場合は、pom.xml ファイルに次の依存関係を追加します。
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

Redis クライアントの作成

Redis データベースを操作するには、コード内で Redis クライアントを作成し、Redis データベースへの接続を管理する必要があります。以降のセクションでは、Jedis クライアント ライブラリを使用して Redis クライアントを作成する方法について説明します。

環境変数の指定

Jedis クライアン トライブラリは、2 つの環境変数を使用して Redis データベースの URL をアセンブルします。

  • Memorystore で作成した Redis データベースの IP アドレスを識別する変数。
  • Memorystore で作成した Redis データベースのポート番号を識別する変数。

コード内で直接定義するのではなく、アプリの app.yaml ファイル内でこれらの変数を定義することをおすすめします。これにより、ローカル環境や App Engine などのさまざまな環境でアプリを実行しやすくなります。

たとえば、app.yaml ファイルに次の行を追加します。

 env_variables:
      redis.host: '10.112.12.112'
      redis.port: '6379'

Jedis のインポートとクライアントの作成

Jedis ライブラリを使用する場合は、JedisPool を作成してから、プールを使用してクライアントを作成することをおすすめします。次のコード行では、前に定義した redis.host 環境変数と redis.port 環境変数を使用してプールを作成しています。


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

プールからクライアントを作成するには、JedisPool.getResource() メソッドを使用します。例:


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Redis コマンドを使用したキャッシュへのデータの保存と取得

Memorystore Redis データベースではほとんどの Redis コマンドがサポートされています。キャッシュからのデータの保存と取得にはいくつかのコマンドが必要になります。次の表に、データのキャッシュに使用できる Redis コマンドを示します。アプリからこれらのコマンドを呼び出す方法については、クライアント ライブラリのドキュメントをご覧ください。

タスク Redis コマンド
データ キャッシュにエントリを作成し、
エントリの有効期限を設定する
SETNX
MSETNX
キャッシュからデータを取得する GET
MGET
既存のキャッシュ値を置き換える SET
MSET
数値キャッシュ値を増減する INCR
INCRBY
DECR
DECRBY
キャッシュからエントリを削除する DEL
UNLINK
キャッシュとの並行処理をサポートする Redis トランザクションの詳細をご覧ください。

アップデートのテスト

アプリをローカルでテストする場合は、Redis のローカル インスタンスを実行して、本番環境用データとの通信を回避してください(Memorystore では、エミュレータは提供されません)。Redis をローカルにインストールして実行するには、Redis のドキュメントにある手順を行ってください。現在、Redis を Windows 上でローカルに実行することはできません。

アプリのデプロイ

アプリをローカル開発用サーバーでエラーなしで実行できれば、次の手順を行います。

  1. App Engine でアプリをテストします。

  2. アプリがエラーなしで実行されている場合、トラフィック分割を使用して、更新したアプリのトラフィックを徐々に増やします。更新したアプリへのトラフィックを増やす前に、データベースの問題が発生していないか細かくモニタリングして確認してください。