このページは、Memorystore for Redis の最適な使用に関するガイダンスです。このページでは、回避すべき潜在的な問題についても説明します。
トラブルシューティングのシナリオの一覧については、トラブルシューティングをご覧ください。
RDB のエクスポート
RDB バックアップをエクスポートする場合は、次のガイダンスを使用します。
- 書き込みレートが低い期間にエクスポートする。
- 書き込みレートが高い期間にエクスポートすると、インスタンス容量の
maxmemory構成を一時的に 50% まで下げ、正常なオペレーションに十分なオーバーヘッドを提供します。
リソースを大量に消費するオペレーション
標準階層の Redis インスタンスの場合、次のオペレーションではオペレーション中に追加のメモリが使用されます。
バージョン アップグレード、スケーリング、手動フェイルオーバーは、レプリケーションのために追加のメモリが使用されます(標準階層の場合)。これらのオペレーションは、標準階層インスタンスのアップグレード動作で説明しているレプリケーション プロセスに従います。
インポート オペレーションとエクスポート オペレーションでは、フォークされた Redis プロセスと、これらのオペレーションに関連付けられたコピーオンライト データ管理のため、追加のメモリが必要です。
リソース集約型のオペレーションのデメリットを軽減するには、次を行う必要があります。
- オペレーション中に、maxmemory 構成をインスタンスの容量の 80% に引き下げます。これにより、正常なオペレーションに十分なオーバーヘッドが操作が提供されます。
- システムメモリ使用率の指標をモニタリングして、これらのオペレーションのいずれかを実行する前に、この指標が 80% を下回ることを確認してください。
- インスタンスのトラフィックが少ない期間(夜間や週末など)に、これらのオペレーションを実行します。
- これらのオペレーションを実行する前に、指数バックオフで再試行ロジックを実行しておく必要があります。
接続の再試行が必要なオペレーションとシナリオ
次の操作とシナリオでは、ネットワークと Redis インスタンスの間のネットワーク接続が切断されます。
- バージョン アップグレード
- スケールアップとスケールダウン
- インポートしています
- 手動フェイルオーバー
- システム メンテナンス
- 転送中の暗号化が有効になっている 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 コマンドの不適切な使用は、高レイテンシ、低い応答性、接続の問題につながります。スタンダード ティアのインスタンスは、障害復旧時に高可用性を提供し、プライマリ ノードとレプリカノード間の非同期レプリケーションに依存します。ノードの 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()