Memorystore for Redis のベスト プラクティス

このページは、Memorystore for Redis の最適な使用に関するガイダンスです。このページでは、回避すべき潜在的な問題についても説明します。

トラブルシューティングのシナリオの一覧については、トラブルシューティングをご覧ください。

高可用性とデータの耐久性のベスト プラクティス

高可用性(HA)の主なメカニズムとして、スタンダード ティアを使用することをおすすめします。スタンダード ティアでは、複数のレプリカを備えた高可用性インスタンスが提供されます。プライマリに障害が発生した場合は、自動フェイルオーバーを使用して高速に復旧できます。さらに、スタンダード ティアのインスタンスで RDB スナップショットを有効にすると、ノード障害に対する保護が強化されます。

場合によっては、スナップショット バックアップからデータを復元できるようにすることも必要です。これらのシナリオでは、バックアップと RDB スナップショットからデータを復元する機能により、データ損失に対する保護を強化できます。

RDB スナップショットを有効にしてデータを復元する必要がある場合、Memorystore for Redis は最新のスナップショットから復元できます。これらのスナップショットは内部システム復元専用であり、アクセスできません。

耐久性の制限とベスト プラクティス

スタンダード ティアでは、レプリケーションと自動フェイルオーバーによって HA が提供されますが、HA ではデータの完全な耐久性は保証されません。Memorystore for Redis は、一時的なリージョン障害などの特定の致命的なイベント中に HA が有効になっている場合でも、完全なデータ耐久性を提供できません。

データを保護し、ベースライン レベルの永続性を提供するため、 Google Cloud コンソールを使用して Memorystore for Redis インスタンスを作成すると、RDB スナップショットがデフォルトで有効になります。

本番環境のワークロードでは、データの復元力をさらに向上させるために、次のベスト プラクティスを検討してください。

  • 障害復旧: Memorystore for Redis は、クロスリージョン レプリケーションをサポートしていません。障害復旧戦略で真のリージョン復元力のためにクロスリージョン レプリケーションが必要な場合は、Memorystore for Valkey インスタンスを使用することをおすすめします。リージョン間でセカンダリ インスタンスを構成して管理する方法については、Memorystore for Valkey のドキュメントのクロスリージョン レプリケーションを操作するをご覧ください。
  • 最大耐久性: アプリケーションで可能な限り高いレベルのデータ耐久性が必要な場合は、クロスリージョン レプリケーション、自動日次バックアップ、追加専用ファイル(AOF)永続性で構成された Memorystore for Valkey インスタンスを使用することをおすすめします。この組み合わせにより、データ損失に対する最も堅牢な保護が実現します。詳細については、Memorystore for Valkey のドキュメントの永続性の概要をご覧ください。

キャパシティ プランニングのベスト プラクティス

リソースを正常にプロビジョニングし、復元力のあるアーキテクチャを構築できるように、次のキャパシティ プランニングのベスト プラクティスをおすすめします。

  • 複数のロケーションにデプロイする: 一時的なリソースの制約により、優先ゾーンにインスタンスを作成できない場合は、同じリージョン内の別のゾーンまたは別のリージョンにインスタンスを作成してみてください。
  • クロスリージョン アクセスを使用する: 異なるリージョンにあるインスタンスにアクセスするようにクライアントを構成します。ローカル リソースの制約により、別のリージョンにあるインスタンスをプロビジョニングする必要がある場合は、クライアントがインスタンスに接続できます。
  • 別の容量ティアを選択する: インスタンスのサイズが原因でリソースの制約が発生した場合は、別の容量ティアを選択するか、インスタンスのサイズを変更します。詳細については、容量ティアのパフォーマンスをご覧ください。

RDB のエクスポート

RDB バックアップをエクスポートする場合は、次のガイダンスを使用します。

リソースを大量に消費するオペレーション

標準階層の Redis インスタンスの場合、次のオペレーションではオペレーション中に追加のメモリが使用されます。

バージョン アップグレード、スケーリング、手動フェイルオーバーは、レプリケーションのために追加のメモリが使用されます(標準階層の場合)。これらのオペレーションは、標準階層インスタンスのアップグレード動作で説明しているレプリケーション プロセスに従います。

インポートとエクスポートのオペレーションでは、フォークされた Redis プロセスと、これらのオペレーションに関連するコピーオンライト データ マネジメントのため、追加のメモリが必要になります。

リソース集約型のオペレーションのデメリットを軽減するには、次を行う必要があります。

  • オペレーション中に、maxmemory 構成をインスタンスの容量の 80% に引き下げます。これにより、正常なオペレーションに十分なオーバーヘッドが操作が提供されます。
  • システムメモリ使用率の指標をモニタリングして、これらのオペレーションのいずれかを実行する前に、この指標が 80% を下回ることを確認してください。
  • これらのオペレーションは、インスタンスのトラフィックが少ない時間帯(夜間や週末など)に実行します。
  • これらのオペレーションを実行する前に、指数バックオフで再試行ロジックを実行しておく必要があります。

接続の再試行が必要なオペレーションとシナリオ

次の操作とシナリオでは、ネットワークと Redis インスタンスの間のネットワーク接続が切断されます。

これらのオペレーションにより、一時的な接続ブレークを必要とするインスタンスが変更されます。アプリケーションが自動的に再接続され、引き続き正常に機能するように、これらのオペレーションを実行する前に指数バックオフで再試行ロジックを設定しておく必要があります。

定期的なメンテナンス

Memorystore for Redis インスタンスは定期的にメンテナンスされます。詳細については、Memorystore for Redis のメンテナンス ポリシーをご覧ください。

定期的なメンテナンスに備えるために、次のベスト プラクティスを実施してください。

メモリ管理

オープンソース Redis では、よく知られるメモリの断片化が発生するため、メモリ管理が難しくなる場合があります。メモリ負荷が高い場合は、インスタンスの maxmemory 構成を下げて、オーバーヘッドを設定することをおすすめします。

Memorystore インスタンスのメモリ プレッシャーをモニタリングする最善の方法は、システムメモリ使用率の指標を使用することです。Memorystore for Redis のメモリを管理する方法について詳しくは、メモリ管理のベスト プラクティスをご覧ください。

アイドル状態の接続の管理

接続が適切に終了されていない場合、時間の経過とともに Memorystore インスタンスへの接続数が増加することがあります。これは特に、容量階層に基づいて最大接続数の制限を課す転送中の暗号化を使用している場合に、パフォーマンスに悪影響を与える可能性があります。この問題を軽減するには、timeout Redis 構成パラメータを使用することをおすすめします。このパラメータを使用すると、アイドル状態のクライアント接続が自動的に終了するまでの秒数を設定できます。

アクセスの透明性のリソース名

機密データを Memorystore for Redis のリソース名に保存しないでください。リソース名とは、Memorystore for Redis インスタンス名と、タグなどのインスタンス メタデータを指します。リソース名に保存されたデータは、 Google Cloud アクセスの透明性によって保護されるとは限りません。また、組織のアクセスの透明性のコンプライアンス要件と競合する可能性があります。

一部のサーバーレス環境に必要なサーバーレス VPC アクセス コネクタ

一部のサーバーレス環境では、Memorystore for Redis に接続するためにサーバーレス VPC アクセス コネクタが必要です。これらの環境のいずれかを使用して接続する場合は、プロジェクト用のサーバーレス VPC アクセス コネクタを設定します。

ネットワーキング

プライベート サービス アクセスの接続モードを使用することをおすすめします。Memorystore for Redis では、プライベート サービス アクセスとダイレクト ピアリングの 2 つの接続モードを使用します。プライベート サービス アクセス接続モードでは、IP 範囲の管理がシンプルになり、必要に応じて共有 VPC を使用できます。

インスタンスを作成した後に、接続モードを変更することはできません。

詳しくは、ネットワーキングをご覧ください。

モニタリングとアラート

Redis インスタンスのメモリ使用量に関する主要な指標を得るために、モニタリングとアラートの使用をおすすめします。また、受信するキャッシュ リクエストに対して Redis インスタンスがどの程度効率的に応答しているかもわかります。

次のデフォルトのアラートを設定する必要があります。

CPU 使用率のベスト プラクティス

高コストな redis コマンドの不適切な使用は、高レイテンシ、低い応答性、接続の問題につながります。スタンダード ティアのインスタンスは、障害復旧時に HA を提供し、プライマリ ノードとレプリカノード間の非同期レプリケーションに依存します。ノードの 1 つに Redis メインスレッドをブロックする高コストのコマンド処理がある場合、レプリケーションに影響する可能性があります。問題が解決せず、ロケーションが停止した場合は、停止したロケーションに書き込まれた最新のデータが他のロケーションで使用できない場合があります。

Cloud Monitoring を使用して、メインスレッド CPU 秒(redis.googleapis.com/stats/cpu_utilization_main_thread)指標にアラートを設定し、レプリカがリードレプリカとして指定されている場合に、CPU 使用率がプライマリ ノードで 0.8 秒、各レプリカノードで 0.5 秒を超えないようにすることをおすすめします。

Redis インスタンスが推奨値を超えている場合は、インスタンスを上位の容量ティアにスケーリングするか、CPU 使用率の高いオペレーションを回避するためにトラブルシューティング手順に従うことをおすすめします。

インスタンスの CPU 使用率が高い場合や、インスタンスのリソースが枯渇している場合(接続数が多すぎる場合など)、インスタンスが誤動作し、外部指標が欠落する可能性があります。

リソースを大量に消費するコマンド

リソースを大量に消費する Redis コマンドは使用しないことを強くおすすめします。これらのコマンドを使用すると、次のようなパフォーマンスの問題が発生する可能性があります。

  • 高レイテンシとクライアント タイムアウト
  • メモリ使用量を増加させるコマンドが原因のメモリ不足
  • Redis メインスレッドがブロックされているため、ノードのレプリケーションと同期中にデータが失われる
  • ヘルスチェック、オブザーバビリティ、レプリケーションの枯渇

次の表に、リソースを大量に消費する Redis コマンドの例と、リソース効率の高い代替コマンドを示します。

カテゴリ リソースを大量に消費するコマンド リソース効率の高い代替手段
キースペース全体に対して実行する KEYS SCAN
可変長鍵セットに対して実行する LRANGE クエリに使用する範囲のサイズを制限します。
ZRANGE クエリに使用する範囲のサイズを制限します。
HGETALL HSCAN
SMEMBERS SSCAN
スクリプトの実行をブロックする EVAL スクリプトが無期限に実行されないようにします。
EVALSHA スクリプトが無期限に実行されないようにします。
ファイルとリンクを削除する DEL UNLINK
パブリッシュとサブスクライブ PUBLISH SPUBLISH
SUBSCRIBE SSUBSCRIBE

Redis クライアントのベスト プラクティス

このセクションでは、Redis クライアントを最適に使用するためのガイダンスを提供します。

応答しない接続を検出して処理する

Memorystore for Redis への応答しない接続を検出するようにクライアント アプリケーションを構成することを強くおすすめします。応答しない接続が検出された場合、クライアントは接続をリセットする必要があります。復元力のあるアプリケーションを構築するには、次のクライアント構成をおすすめします。

  • TCP keep-alive パラメータを構成する: TCP keepalive time、TCP keepalive interval、TCP keepalive probes パラメータを設定して、接続がアイドル状態の場合でも、クライアントが応答しない接続をプロアクティブに検出して切断するようにします。たとえば、TCP keepalive time パラメータを 30 秒、TCP keepalive interval を 10 秒、TCP keepalive probes を 3 に設定すると、クライアントは応答しないアイドル状態の接続を 1 分以内にリセットします。
  • TCP ユーザー タイムアウトを構成する: このタイムアウトをクライアントに設定して、未処理のリクエストがあり、応答を停止した接続をリセットします。たとえば、タイムアウトを 15 秒に設定すると、クライアントは 15 秒後に未処理のリクエストがある応答しない接続をリセットします。

クライアント固有のベスト プラクティス

アプリケーションをスケールアウトすると、書き込みコマンドで READONLY エラーが発生することがあります。Memorystore for Redis は、静的エンドポイントを持つ非 Sentinel デプロイを使用します。クライアントは、動的ロール情報を事前に受信しません。プライマリ エンドポイントとレプリカ エンドポイントの両方に単一のクライアント接続を使用すると、クライアントが誤って読み取り専用レプリカに書き込みコマンドを送信する可能性があります。レプリカは書き込みコマンドを処理できないため、READONLY エラーを返します。

書き込みの誤ルーティングを防ぐため、読み取りと書き込みの両方に単一の接続を使用しないでください。代わりに、次の個別のクライアント インスタンスを作成してオペレーションを分割します。

  • プライマリ クライアント: プライマリ エンドポイントにのみ接続します。
  • レプリカ クライアント: レプリカ エンドポイントにのみ接続します。

次のタブでは、Go、Java、Node.js、Python で個別のテンプレートを構成する方法について説明します。

Go

package main

import (
  "context"
  "fmt"

  "github.com/redis/go-redis/v9"
)

func main() {
ctx := context.Background()

  // Initialize the primary client connecting only to the primary endpoint
  primaryClient := redis.NewClient(&redis.Options{
      Addr:     "PRIMARY_HOST:6379",
      Password: "YOUR_PASSWORD",
      DB:       0,
  })
  defer primaryClient.Close()

  // Initialize the replica client connecting only to the replica endpoint
  replicaClient := redis.NewClient(&redis.Options{
      Addr:     "REPLICA_HOST:6379",
      Password: "YOUR_PASSWORD",
      DB:       0,
  })
  defer replicaClient.Close()

  // Use the primary client for all mutating commands
  err := primaryClient.Set(ctx, "example_key", "example_value", 0).Err()
  if err != nil {
      fmt.Printf("Failed to write to primary: %v\n", err)
  }

  // Use the replica client for all read-only commands
  val, err := replicaClient.Get(ctx, "example_key").Result()
  if err != nil {
      fmt.Printf("Failed to read from replica: %v\n", err)
  } else {
      fmt.Printf("Successfully read value: %s\n", val)
  }
}

Java

@Bean
public RedisConnectionFactory primaryConnectionFactory() {
  RedisStandaloneConfiguration config = new RedisStandaloneConfiguration("PRIMARY_HOST", 6379);
  config.setPassword(RedisPassword.of("YOUR_PASSWORD"));
  return new LettuceConnectionFactory(config);
}

@Bean
public RedisConnectionFactory replicaConnectionFactory() {
  RedisStaticMasterReplicaConfiguration config =
      new RedisStaticMasterReplicaConfiguration("PRIMARY_HOST", 6379);
  config.addNode("REPLICA_HOST", 6379);
  config.setPassword(RedisPassword.of("YOUR_PASSWORD"));

  LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
      .readFrom(ReadFrom.REPLICA_PREFERRED)
      .build();

  return new LettuceConnectionFactory(config, clientConfig);
}

@Bean
public RedisTemplate<String, Object> primaryRedisTemplate(
  @Qualifier("primaryConnectionFactory") RedisConnectionFactory factory) {

  RedisTemplate<String, Object> template = new RedisTemplate<>();
  template.setConnectionFactory(factory);
  return template;
}

@Bean
public RedisTemplate<String, Object> replicaRedisTemplate(
  @Qualifier("replicaConnectionFactory") RedisConnectionFactory factory) {

  RedisTemplate<String, Object> template = new RedisTemplate<>();
  template.setConnectionFactory(factory);
  return template;
}

Node.js

import { createClient } from 'redis';

async function main() {
// Initialize the primary client connecting only to the primary endpoint
const primaryClient = createClient({
  url: 'redis://PRIMARY_HOST:6379',
  password: 'YOUR_PASSWORD'
});

// Initialize the replica client connecting only to the replica endpoint
const replicaClient = createClient({
  url: 'redis://REPLICA_HOST:6379',
  password: 'YOUR_PASSWORD'
});

primaryClient.on('error', (err) => console.error('Primary Client Error', err));
replicaClient.on('error', (err) => console.error('Replica Client Error', err));

await primaryClient.connect();
await replicaClient.connect();

// Use the primary client for all mutating commands
try {
  await primaryClient.set('example_key', 'example_value');
  console.log('Successfully wrote to primary');
} catch (err) {
  console.error('Failed to write to primary:', err);
}

// Use the replica client for all read-only commands
try {
  const val = await replicaClient.get('example_key');
  console.log(`Successfully read value: ${val}`);
} catch (err) {
  console.error('Failed to read from replica:', err);
}

await primaryClient.disconnect();
await replicaClient.disconnect();
}

main();

Python

import redis

def main():
  # Initialize the primary client connecting only to the primary endpoint
  primary_client = redis.Redis(
      host='PRIMARY_HOST',
      port=6379,
      password='YOUR_PASSWORD',
      decode_responses=True
  )

  # Initialize the replica client connecting only to the replica endpoint
  replica_client = redis.Redis(
      host='REPLICA_HOST',
      port=6379,
      password='YOUR_PASSWORD',
      decode_responses=True
  )

  # Use the primary client for all mutating commands
  try:
      primary_client.set('example_key', 'example_value')
      print('Successfully wrote to primary')
  except redis.RedisError as e:
      print(f'Failed to write to primary: {e}')

  # Use the replica client for all read-only commands
  try:
      val = replica_client.get('example_key')
      print(f'Successfully read value: {val}')
  except redis.RedisError as e:
      print(f'Failed to read from replica: {e}')

if __name__ == '__main__':
  main()