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

    Memorystore for Redis は Redis 用クライアント ライブラリと互換性があります。

    Go

    このガイドでは、アプリから Redis コマンドを送信する際の redigo クライアント ライブラリの使用について説明します。

    Java

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

    Node.js

    このガイドでは、アプリから Redis コマンドを送信する際の node_redis クライアント ライブラリの使用について説明します。

    PHP

    このガイドでは、アプリから Redis コマンドを送信する際の PHPRedis クライアント ライブラリの使用について説明します。

    Python

    このガイドでは、アプリから Redis コマンドを送信する際の redis-py 3.0 クライアント ライブラリの使用について説明します。

    Ruby

    このガイドでは、アプリから Redis コマンドを送信する際の redis-rb クライアント ライブラリの使用について説明します。

  3. 更新をテストする

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

Memorystore for Redis のセットアップ

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

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

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

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

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

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

依存関係のインストール

Go

アプリが App Engine で実行されているときに、redigo クライアント ライブラリをアプリで使用できるようにするには、ライブラリをアプリの依存関係に追加します。たとえば、go.mod ファイルを使用して依存関係を宣言するには、次の行を go.mod ファイルに追加します。

module github.com/GoogleCloudPlatform/golang-samples/tree/master/memorystore/redis

詳細については、Go アプリの依存関係の指定をご覧ください。

Java

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

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

Node.js

アプリが App Engine で実行されているときに node_redis クライアント ライブラリを使用できるようにするには、ライブラリをアプリの package.json ファイルに追加します。

次に例を示します。

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "redis": "^4.0.0"
  }
}

詳しくは、Node.js アプリの依存関係の指定をご覧ください。

PHP

アプリが App Engine で実行されているときに PHPRedis クライアント ライブラリを使用できるようにするには、アプリの php.ini ファイルに redis.so 拡張子を追加します。次に例を示します。

; Enable the Redis extension on App Engine
extension=redis.so

App Engine で PHP 拡張機能を有効にする方法については、動的に読み込み可能な拡張機能をご覧ください。

Python

アプリが App Engine で実行されているときに redis-py クライアント ライブラリを使用できるようにするには、アプリの requirements.txt ファイルに次の行を追加します。

  redis

App Engine Python 3 ランタイムは、アプリをデプロイするときに、アプリの requirements.txt ファイルのすべてのライブラリを自動的にアップロードします。

ローカル開発では、venv などの仮想環境に依存関係をインストールすることをおすすめします。

Ruby

アプリが App Engine で実行されているときに redis-rb クライアント ライブラリを使用できるようにするには、ライブラリをアプリの Gemfile ファイルに追加します。

  source "https://cloud.google.com/memorystore"

  gem "redis-rb"

Redis クライアントの作成

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

環境変数の指定

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

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

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

Go

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

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Java

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

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

Node.js

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

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

PHP

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

  env_variables:
       REDIS_HOST: '10.112.12.112'
       REDIS_PORT: '6379'

Python

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

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

Ruby

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

  env_variables:
       REDISHOST: '10.112.12.112'
       REDISPORT: '6379'

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

Go

REDISHOST 環境変数と REDISPORT 環境変数を定義したら、次の行を使用して、redigo ライブラリをインポートし、接続プールを作成して、プールから Redis クライアントを取得します。


// Command redis is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Java

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());
    }
  }
}

Node.js

環境変数 REDISHOSTREDISPORT を定義したら、次のコードを使用して node_redis ライブラリのインポートと Redis クライアントの作成を行うことができます。

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

PHP

REDIS_HOST 環境変数と REDIS_PORT 環境変数を定義したら、次の行を使用して Redis クライアントを作成できます。

<?php
/**
 * Copyright 2019 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Only serve traffic from "/"
switch (@parse_url($_SERVER['REQUEST_URI'])['path']) {
    case '/':
        break;
    default:
        http_response_code(404);
        exit('Not Found');
}

// Connect to Memorystore from App Engine.
if (!$host = getenv('REDIS_HOST')) {
    throw new Exception('The REDIS_HOST environment variable is required');
}

# Memorystore Redis port defaults to 6379
$port = getenv('REDIS_PORT') ?: '6379';

try {
    $redis = new Redis();
    $redis->connect($host, $port);
} catch (Exception $e) {
    return print('Error: ' . $e->getMessage());
}

$value = $redis->incr('counter');

printf('Visitor number: %s', $value);

Python

REDISHOSTREDISPORT を定義した後に、次の行を使用して redis-py ライブラリをインポートしてクライアントを作成します。

  import redis

  redis_host = os.environ.get('REDISHOST', 'localhost')
  redis_port = int(os.environ.get('REDISPORT', 6379))
  redis_client = redis.Redis(host=redis_host, port=redis_port)

古いバージョンの redis-py をその他のアプリで使用している場合は、Client ではなく StrictClient クラスを使用しているかもしれません。redis-py では、StrictClient ではなく Client の使用をおすすめします。

Ruby

このランタイムの追加情報はありません。

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

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

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

Python の場合、redis-py クライアント ライブラリでは、すべてのトランザクションがパイプラインで発生する必要があります。

アップデートのテスト

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

アプリのテストの詳細については、アプリケーションのテストとデプロイをご覧ください。

アプリのデプロイ

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

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

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