

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# クライアントのベストプラクティス (Valkey および Redis OSS)
<a name="BestPractices.Clients.redis"></a>

一般的なシナリオのベストプラクティスを説明し、最も一般的なオープンソースの Valkey および Redis OSS クライアントライブラリ (redis-py、PHPRedis、Lettuce) のコード例、よく使用されるオープンソースの Memcached クライアントライブラリで ElastiCache リソースを操作するためのベストプラクティスについて説明します。

**Topics**
+ [多数の接続 (Valkey および Redis OSS)](BestPractices.Clients.Redis.Connections.md)
+ [クラスターのクライアント検出とエクスポネンシャルバックオフ (Valkey および Redis OSS)](BestPractices.Clients.Redis.Discovery.md)
+ [クライアント側のタイムアウトを設定する (Valkey および Redis OSS)](BestPractices.Clients.Redis.ClientTimeout.md)
+ [サーバー側のアイドルタイムアウトの設定 (Valkey および Redis OSS)](BestPractices.Clients.Redis.ServerTimeout.md)
+ [Lua スクリプト](BestPractices.Clients.Redis.LuaScripts.md)
+ [大きな複合アイテムの保存 (Valkey および Redis OSS)](BestPractices.Clients.Redis.LargeItems.md)
+ [Lettuce クライアント設定 (Valkey および Redis OSS)](BestPractices.Clients-lettuce.md)
+ [デュアルスタッククラスターの優先プロトコルの設定 (Valkey および Redis OSS)](#network-type-configuring-dual-stack-redis)

# 多数の接続 (Valkey および Redis OSS)
<a name="BestPractices.Clients.Redis.Connections"></a>

サーバーレスキャッシュと個々の ElastiCache for Redis OSS ノードは、最大 65,000 の同時クライアント接続をサポートしています。ただし、パフォーマンスを最適化するために、クライアントアプリケーションが常にはそのレベルの接続で動作しないことをお勧めします。Valkey および Redis OSS にはそれぞれ、クライアントからの受信リクエストが順番に処理されるイベントループに基づくシングルスレッドのプロセスがあります。つまり、接続しているクライアントの数が増えると、特定のクライアントの応答時間が長くなります。

Valkey または Redis OSS サーバーで接続のボトルネックを回避するために、以下の対策を実行します。
+ リードレプリカからの読み取り操作を実行する。これは、クラスターモードを無効にした状態で ElastiCache リーダーエンドポイントを使用するか、サーバーレスキャッシュを含め、クラスターモードを有効にした状態で読み取り用のレプリカを使用することで実行できます。
+ 書き込みトラフィックを複数のプライマリノードに分散する。これには 2 つの方法があります。マルチシャード Valkey または Redis OSS クラスターは、クラスターモード対応のクライアントで使用できます。また、クライアント側のシャーディングで無効になっているクラスターモードで、複数のプライマリノードに書き込むこともできます。これはサーバーレスキャッシュで自動的に行われます。
+ クライアントライブラリで利用可能な場合は、接続プールを使用する。

一般に、TCP 接続の作成は、一般的な Valkey または Redis OSS コマンドに比べて計算コストが高いオペレーションです。例えば、既存の接続を再利用すると、SET/GET リクエストの処理が桁違いに速くなります。有限サイズのクライアント接続プールを使用すると、接続管理のオーバーヘッドが軽減されます。また、クライアントアプリケーションからの同時着信接続数も制限されます。

次の PHPRedis のコード例は、新しいユーザーのリクエストごとに新しい接続が作成されることを示しています。

```
$redis = new Redis();
if ($redis->connect($HOST, $PORT) != TRUE) {
	//ERROR: connection failed
	return;
}
$redis->set($key, $value);
unset($redis);
$redis = NULL;
```

このコードは、Graviton2 (m6g.2xlarge) ElastiCache for Redis OSS ノードに接続された Amazon Elastic Compute Cloud (Amazon EC2) インスタンス上のループでベンチマークされています。クライアントとサーバーは同じアベイラビリティーゾーンに配置されています。オペレーション全体の平均レイテンシーは 2.82 ミリ秒でした。

コードを更新して永続的な接続と接続プールを使用した場合、オペレーション全体の平均レイテンシーは 0.21 ミリ秒でした。

```
$redis = new Redis();
if ($redis->pconnect($HOST, $PORT) != TRUE) {
	// ERROR: connection failed
	return;
}
$redis->set($key, $value);
unset($redis);
$redis = NULL;
```

必要な redis.ini の設定:
+ `redis.pconnect.pooling_enabled=1`
+ `redis.pconnect.connection_limit=10`

以下のコードは [Redis-py 接続プール](https://redis.readthedocs.io/en/stable/)の例です。

```
conn = Redis(connection_pool=redis.BlockingConnectionPool(host=HOST, max_connections=10))
conn.set(key, value)
```

以下のコードは [Lettuce 接続プール](https://lettuce.io/core/release/reference/#_connection_pooling)の例です。

```
RedisClient client = RedisClient.create(RedisURI.create(HOST, PORT));
GenericObjectPool<StatefulRedisConnection> pool = ConnectionPoolSupport.createGenericObjectPool(() -> client.connect(), new GenericObjectPoolConfig());
pool.setMaxTotal(10); // Configure max connections to 10
try (StatefulRedisConnection connection = pool.borrowObject()) {
	RedisCommands syncCommands = connection.sync();
	syncCommands.set(key, value);
}
```

# クラスターのクライアント検出とエクスポネンシャルバックオフ (Valkey および Redis OSS)
<a name="BestPractices.Clients.Redis.Discovery"></a>

クラスターモードを有効にして ElastiCache Valkey または Redis OSS クラスターに接続する場合、該当するクライアントライブラリがクラスター対応であることが必要です。クライアントは、適切なノードにリクエストを送信し、クラスターのリダイレクト処理によるパフォーマンスのオーバーヘッドを回避できるように、ハッシュスロットとクラスター内のノードを対応付けたマップを取得する必要があります。そのため、クライアントは以下の 2 つの異なる状況下で、スロットとマッピング先のノードを網羅したリストを検出する必要があります。
+ クライアントが初期化され、初期スロット構成を読み込む必要がある。
+ MOVED リダイレクトをサーバーから受信した。例えば、フェイルオーバー時に元のプライマリノードのスロットをすべてレプリカに引き継ぐ場合や、リシャーディング時にスロットがソースプライマリノードからターゲットプライマリノードに移動される場合が該当します。

クライアント検出は通常、Valkey または Redis OSS サーバーに CLUSTER SLOT コマンドまたは CLUSTER NODE コマンドを発行することで行われます。CLUSTER SLOT メソッドを推奨します。その理由は、一連のスロット範囲と、関連するプライマリノードとレプリカノードをクライアントに返すからです。その場合、クライアント側で別途解析を行う必要がなく、効率が向上します。

クラスタートポロジーによっては、CLUSTER SLOT コマンドの応答のサイズがクラスターのサイズに応じて変わる場合があります。ノード数が多い大きなクラスターは、応答も大きくなります。したがって、クラスタートポロジー検出を行うクライアントの数が、際限なく増えないようにすることが重要です。例えば、クライアントアプリケーションの起動時やサーバーとの接続の切断時にクラスター検出を実行しなければならない場合に、クライアントアプリケーションで再接続や検出のリクエストを複数回行い、再試行時のエクスポネンシャルバックオフが実装されていないという間違いがよく見受けられます。そのせいで CPU 使用率が 100% になり、そのまま Valkey または Redis OSS サーバーが長時間応答しなくなる可能性があります。各 CLUSTER SLOT コマンドでクラスターバス内の多数のノードを処理しなければならない場合は、こうした停止状態が長引きます。このような動作が原因でクライアントが停止する事態は、これまでも、Python (redis-py-cluster) や Java (Lettuce と Redisson) などのさまざまな言語でいくつも確認されています。

サーバーレスキャッシュでは、アドバタイズされるクラスタートポロジが静的であり、書き込みエンドポイントと読み取りエンドポイントの 2 つのエントリで構成されるため、こうした問題の多くは自動的に軽減されます。また、キャッシュエンドポイントを使用する場合、クラスター検出が自動的に複数のノードに分散されます。ただし、以下の推奨事項は引き続き有効です。

接続リクエストや検出リクエストが殺到した場合の影響を軽減するために、以下の対応を推奨します。
+ クライアントアプリケーションからの同時着信接続数を制限するために、有限サイズのクライアント接続プールを実装する。
+ タイムアウトによりクライアントがサーバーから切断された場合は、エクスポネンシャルバックオフとジッター(揺らぎ) を加えて再試行する。これにより、複数のクライアントが同時にサーバーに負荷をかける事態を阻止できます。
+ 「[ElastiCache での接続エンドポイントの検索](Endpoints.md)」のガイドを参考にして、クラスターエンドポイントを検索し、クラスター検出を実行する。これにより、クラスター内のハードコーディングされたいくつかのシードノードにアクセスする代わりに、検出の負荷をクラスター内のすべてのノード (最大 90 個) 間で分散できます。

redis-py、PHPRedis、Lettuce におけるエクスポネンシャルバックオフの再試行ロジックのコード例を以下に紹介します。

**バックオフロジックのサンプル 1: redis-py**

redis-py には、障害の発生直後に 1 回再試行する再試行メカニズムが組み込まれています。このメカニズムは、[Redis OSS](https://redis.readthedocs.io/en/stable/examples/connection_examples.html#redis.Redis) オブジェクトの作成時に渡される `retry_on_timeout` 引数で有効にすることができます。ここでは、エクスポネンシャルバックオフとジッターを加えたカスタムの再試行メカニズムを紹介します。[redis-py (\$11494)](https://github.com/andymccurdy/redis-py/pull/1494) で、エクスポネンシャルバックオフをネイティブに実装するプルリクエストを送信しました。今後は、手動で実装する必要がなくなる可能性があります。

```
def run_with_backoff(function, retries=5):
base_backoff = 0.1 # base 100ms backoff
max_backoff = 10 # sleep for maximum 10 seconds
tries = 0
while True:
try:
  return function()
except (ConnectionError, TimeoutError):
  if tries >= retries:
	raise
  backoff = min(max_backoff, base_backoff * (pow(2, tries) + random.random()))
  print(f"sleeping for {backoff:.2f}s")
  sleep(backoff)
  tries += 1
```

その後、以下のコードを使用して値を設定できます。

```
client = redis.Redis(connection_pool=redis.BlockingConnectionPool(host=HOST, max_connections=10))
res = run_with_backoff(lambda: client.set("key", "value"))
print(res)
```

ワークロードに応じて、例えば、レイテンシーの影響を受けやすいワークロードについては、バックオフの基準値を 1 秒から数十ミリ秒または数百ミリ秒に変更することができます。

**バックオフロジックのサンプル 2: PHPRedis**

PHPRedis には、最大 10 回 (回数設定は不可) 再試行する再試行メカニズムが組み込まれています。試行間隔の遅延を設定できます (2 回目以降にジッターを加えます)。詳細については、[こちらのサンプルコード](https://github.com/phpredis/phpredis/blob/b0b9dd78ef7c15af936144c1b17df1a9273d72ab/library.c#L335-L368)を参照してください。[PHPRedis (\$11986)](https://github.com/phpredis/phpredis/pull/1986) で、エクスポネンシャルバックオフをネイティブに実装するプルリクエストを送信しました。このリクエストはその後統合され、[文書化](https://github.com/phpredis/phpredis/blob/develop/README.md#retry-and-backoff)されています。PHPRedis の最新リリースを使用している場合は、手動で実装する必要はありませんが、以前のバージョンを使用している場合の参考資料として、ここで紹介しておきます。差し当たり、再試行メカニズムの遅延を設定するコード例を以下に紹介します。

```
$timeout = 0.1; // 100 millisecond connection timeout
$retry_interval = 100; // 100 millisecond retry interval
$client = new Redis();
if($client->pconnect($HOST, $PORT, $timeout, NULL, $retry_interval) != TRUE) {
	return; // ERROR: connection failed
}
$client->set($key, $value);
```

**バックオフロジックのサンプル 3: Lettuce**

Lettuce には、[エクスポネンシャルバックオフとジッター](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)の投稿で説明したエクスポネンシャルバックオフ戦略に基づく再試行メカニズムが組み込まれています。以下は、フルジッターのアプローチを示すコードの抜粋です。

```
public static void main(String[] args)
{
	ClientResources resources = null;
	RedisClient client = null;

	try {
		resources = DefaultClientResources.builder()
				.reconnectDelay(Delay.fullJitter(
			Duration.ofMillis(100),     // minimum 100 millisecond delay
			Duration.ofSeconds(5),      // maximum 5 second delay
			100, TimeUnit.MILLISECONDS) // 100 millisecond base
		).build();

		client = RedisClient.create(resources, RedisURI.create(HOST, PORT));
		client.setOptions(ClientOptions.builder()
	.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(100)).build()) // 100 millisecond connection timeout
	.timeoutOptions(TimeoutOptions.builder().fixedTimeout(Duration.ofSeconds(5)).build()) // 5 second command timeout
	.build());

	    // use the connection pool from above example
	} finally {
		if (connection != null) {
			connection.close();
		}

		if (client != null){
			client.shutdown();
		}

		if (resources != null){
			resources.shutdown();
		}

	}
}
```

# クライアント側のタイムアウトを設定する (Valkey および Redis OSS)
<a name="BestPractices.Clients.Redis.ClientTimeout"></a>

**クライアント側のタイムアウトを設定する**

サーバーがリクエストを処理してレスポンスを生成するのに十分な時間を確保できるように、クライアント側のタイムアウトを適切に設定します。また、これにより、サーバーへの接続を確立できない場合でも、フェイルファストが可能です。Valkey または Redis OSS コマンドの中には、他のコマンドよりも計算コストが高いものがあります。例えば、アトミックに実行する必要のある複数のコマンドを含む Lua スクリプトや MULTI/EXEC トランザクションなどです。一般的には、以下を含むサーバーからレスポンスを受け取る前にクライアントがタイムアウトするのを避けるため、クライアント側のタイムアウト時間を長くすることが推奨されます。
+ 複数のキーにまたがるコマンドの実行
+ 複数の個別の Valkey または Redis OSS コマンドで構成される MULTI/EXEC トランザクションまたは Lua スクリプトの実行
+ 大きな値の読み取り
+ BLPOP などのブロッキング操作の実行

BLPOP のようなブロッキング操作の場合、ベストプラクティスとして、コマンドのタイムアウトをソケットのタイムアウトよりも小さい数値に設定します。

redis-py、PHPRedis、および Lettuce でクライアント側のタイムアウトを実装するコード例を以下に示します。

**タイムアウトの設定例 1: redis-py**

redis-py を使用したコード例は次のとおりです。

```
# connect to Redis server with a 100 millisecond timeout
# give every Redis command a 2 second timeout
client = redis.Redis(connection_pool=redis.BlockingConnectionPool(host=HOST, max_connections=10,socket_connect_timeout=0.1,socket_timeout=2))

res = client.set("key", "value") # will timeout after 2 seconds
print(res)                       # if there is a connection error

res = client.blpop("list", timeout=1) # will timeout after 1 second
                                      # less than the 2 second socket timeout
print(res)
```

**タイムアウトの設定例 2: PHPRedis**

PHPRedis を使用したコード例は次のとおりです。

```
// connect to Redis server with a 100ms timeout
// give every Redis command a 2s timeout
$client = new Redis();
$timeout = 0.1; // 100 millisecond connection timeout
$retry_interval = 100; // 100 millisecond retry interval
$client = new Redis();
if($client->pconnect($HOST, $PORT, 0.1, NULL, 100, $read_timeout=2) != TRUE){
	return; // ERROR: connection failed
}
$client->set($key, $value);

$res = $client->set("key", "value"); // will timeout after 2 seconds
print "$res\n";                      // if there is a connection error

$res = $client->blpop("list", 1); // will timeout after 1 second
print "$res\n";                   // less than the 2 second socket timeout
```

**タイムアウトの設定例 3: Lettuce**

Lettuce を使用したコード例は次のとおりです。

```
// connect to Redis server and give every command a 2 second timeout
public static void main(String[] args)
{
	RedisClient client = null;
	StatefulRedisConnection<String, String> connection = null;
	try {
		client = RedisClient.create(RedisURI.create(HOST, PORT));
		client.setOptions(ClientOptions.builder()
	.socketOptions(SocketOptions.builder().connectTimeout(Duration.ofMillis(100)).build()) // 100 millisecond connection timeout
	.timeoutOptions(TimeoutOptions.builder().fixedTimeout(Duration.ofSeconds(2)).build()) // 2 second command timeout 
	.build());

		// use the connection pool from above example

		commands.set("key", "value"); // will timeout after 2 seconds
		commands.blpop(1, "list"); // BLPOP with 1 second timeout
	} finally {
		if (connection != null) {
			connection.close();
		}

		if (client != null){
			client.shutdown();
		}
	}
}
```

# サーバー側のアイドルタイムアウトの設定 (Valkey および Redis OSS)
<a name="BestPractices.Clients.Redis.ServerTimeout"></a>

お客様のアプリケーションにアイドル状態のクライアントが多数接続されていて、コマンドを活発に送信しているわけではないケースが散見されています。このような場合、多数のアイドル状態のクライアントで 65,000 の接続数を使い果たしてしまう可能性があります。こうした状況を回避するには、[Valkey および Redis OSS パラメータ](ParameterGroups.Engine.md#ParameterGroups.Redis) を使用してサーバーのタイムアウトを適切に設定してください。そうすれば、サーバーがアイドル状態のクライアントの接続を積極的に切断するため、接続数の上昇を防ぐことができます。この設定は、サーバーレスキャッシュでは使用できません。

# Lua スクリプト
<a name="BestPractices.Clients.Redis.LuaScripts"></a>

Valkey および Redis OSS は 200 超のコマンドをサポートしており、その中には、Lua スクリプトを実行するコマンドもあります。ただし、Lua スクリプトに関しては、Valkey または Redis OSS のメモリと可用性に影響しかねない注意点がいくつかあります。

**パラメータ化されていない Lua スクリプト**

各 Lua スクリプトは、実行前に Valkey または Redis OSS サーバーにキャッシュされます。パラメータ化されていない Lua スクリプトはそれぞれ異なるため、Valkey または Redis OSS サーバーで大量の Lua スクリプトが保存され、メモリ消費量が増える可能性があります。これを軽減するには、すべての Lua スクリプトを確実にパラメータ化し、SCRIPT FLUSH を定期的に実行して、キャッシュされている Lua スクリプトを適宜クリーンアップします。

キーを提供する必要があることにも注意してください。KEY パラメータの値が指定されていない場合、スクリプトは失敗します。例えば、次の場合は機能しません。

```
serverless-test-lst4hg.serverless.use1.cache.amazonaws.com:6379> eval 'return "Hello World"' 0
(error) ERR Lua scripts without any input keys are not supported.
```

次の場合は機能します。

```
serverless-test-lst4hg.serverless.use1.cache.amazonaws.com:6379> eval 'return redis.call("get", KEYS[1])' 1 mykey-2
"myvalue-2"
```

次のコード例では、パラメータ化したスクリプトの使い方を紹介します。まず、パラメータ化しない場合の例を紹介します。この場合、3 つの異なる Lua スクリプトがキャッシュされるため、推奨されません。

```
eval "return redis.call('set','key1','1')" 0
eval "return redis.call('set','key2','2')" 0
eval "return redis.call('set','key3','3')" 0
```

代わりに、以下のパターンを使用して、渡されたパラメータを受け入れることができる単一のスクリプトを作成してください。

```
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 key1 1 
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 key2 2 
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 key3 3
```

**実行時間の長い Lua スクリプト**

Lua スクリプトは複数のコマンドをアトミックに実行できるため、通常の Valkey または Redis OSS コマンドよりも所要時間が長くなる場合があります。Lua スクリプトが読み取り専用のオペレーションのみを実行する場合は、途中で停止できます。ただし、Lua スクリプトが書き込みオペレーションを実行した時点で強制終了できなくなり、最後まで実行しなければなりません。変更処理を行う Lua スクリプトの実行時間が長引くと、Valkey または Redis OSS サーバーが長時間応答しなくなる可能性があります。この問題を軽減するには、実行時間の長い Lua スクリプトを避け、実稼働前の環境でスクリプトをテストしてください。

**ステルス書き込みを行う Lua スクリプト**

Valkey または Redis OSS が `maxmemory` を上回っても、Lua スクリプトが引き続き Valkey または Redis OSS に新しいデータを書き込むケースがいくつかあります。
+ Valkey または Redis OSS サーバーが `maxmemory` を下回っている場合にスクリプトが開始し、そのスクリプト内に複数の書き込みオペレーションが含まれている。
+ スクリプトの最初の書き込みコマンドはメモリを消費しないが (DEL など)、後続の複数の書き込みオペレーションがメモリを消費する。
+ Valkey または Redis OSS サーバーで `noeviction` 以外の適切なエビクションポリシーを設定することで、この問題を軽減できます。これにより、Redis OSS は Lua スクリプトの合間にアイテムを削除し、メモリを解放できます。

# 大きな複合アイテムの保存 (Valkey および Redis OSS)
<a name="BestPractices.Clients.Redis.LargeItems"></a>

状況によっては、アプリケーションが大きな複合アイテム (マルチ GB のハッシュデータセットなど) を Valkey または Redis OSS に保存することがあります。これは、Valkey または Redis OSS でパフォーマンスの問題が生じやすくなるため、原則としては推奨されません。例えば、クライアントは HGETALL コマンドを実行して、マルチ GB のハッシュコレクション全体を取得できます。この場合、クライアントの出力バッファに大きなアイテムがバッファリングされ、Valkey または Redis OSS サーバーに多大なメモリ負荷がかかる可能性があります。また、クラスターモードでのスロット移行では、ElastiCache はシリアル化されたサイズが 256 MB を超えるアイテムを含むスロットを移行しません。

大きいアイテムの問題を解決するために、以下の点を推奨します。
+ 大きな複合アイテムは複数の小さなアイテムに分割する。例えば、大きなハッシュコレクションを、そのコレクションを適切に反映したキー名スキームを使用して (アイテムのコレクションを識別する共通のプレフィックスをキー名に付けるなど)、個々のキーと値のフィールドに分割します。同じコレクション内の複数のフィールドにアトミックにアクセスする必要がある場合は、MGET コマンドを使用して、同一コマンドで複数のキーと値のペアを取得できます。
+ どの方法を検討しても大きなコレクションデータセットを分割できない場合は、コレクション全体ではなく、コレクション内のデータのサブセットを操作するコマンドを使用してみる。マルチ GB のコレクション全体を同一コマンドでアトミックに取得しなければならないようなユースケースは避けてください。ハッシュコレクションに対して HGETALL の代わりに HGET コマンドや HMGET コマンドを使用することが、その一例です。

# Lettuce クライアント設定 (Valkey および Redis OSS)
<a name="BestPractices.Clients-lettuce"></a>

このセクションでは、推奨される Java と Lettuce の設定オプションと、それらを ElastiCache クラスターに適用する方法について説明します。

このセクションの推奨事項は、Lettuce バージョン 6.2.2 でテスト済みです。

**Topics**
+ [例: TLS が有効な場合のクラスターモードの Lettuce の設定](BestPractices.Clients-lettuce-cme.md)
+ [例: TLS が有効でクラスターモードが無効な場合の Lettuce の設定](BestPractices.Clients-lettuce-cmd.md)

**Java DNS キャッシュ TTL**

Java 仮想マシン (JVM) は DNS 名参照をキャッシュします。JVM がホスト名を IP アドレスに変換するとき、*time-to-live* (TTL) と呼ばれる指定期間 IP アドレスをキャッシュします。

TTL 値の選択は、レイテンシーおよび変化に対する応答性と間のトレードオフです。TTL を短くすると、DNS リゾルバーはクラスターの DNS の更新をより早く認識します。これにより、クラスターで実行される置換やその他のワークフローにアプリケーションがより迅速に応答できるようになります。ただし、TTL が低すぎると、クエリの量が増え、それによってアプリケーションのレイテンシーが増加する可能性があります。絶対的に正しい TTL 値は存在しませんが、TTL 値を設定するときは、変更が有効になるまで待つことができる時間の長さについて検討する必要があります。

ElastiCache ノードは、変更される可能性がある DNS 名を使用するため、5～10 秒の低い TTL 値で JVM を設定することをお勧めします。これにより、ノードの IP アドレスが変更されたときに、アプリケーションは DNS エントリに対して再度クエリを実行することで、リソースの新しい IP アドレスを取得し、使用できるようになります。

一部の Java 設定では JVM のデフォルトの TTL が設定されるため、JVM が再起動されるまで、DNS エントリが更新されることはありません。

JVM TTL を設定する方法の詳細については、「[JVM TTL を設定する方法](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-jvm-ttl.html#how-to-set-the-jvm-ttl)」を参照してください。

**Lettuce のバージョン**

Lettuce のバージョン 6.2.2 以降の使用をお勧めします。

**エンドポイント**。

クラスターモードが有効なクラスターを使用している場合は、`redisUri` をクラスター設定エンドポイントに設定します。この URI の DNS ルックアップは、クラスターで使用可能なすべてのノードのリストを返し、クラスターの初期化中にそれらのノードの 1 つにランダムに解決されます。トポロジ更新の仕組みの詳細については、このトピックで後述する「*DynamicRefreshResources*」を参照してください。

**SocketOption**

[KeepAlive](https://lettuce.io/core/release/api/io/lettuce/core/SocketOptions.KeepAliveOptions.html) を有効にします。このオプションを有効にすると、コマンドのランタイムに失敗した接続を処理する必要が減ります。

[接続タイムアウトは](https://lettuce.io/core/release/api/io/lettuce/core/SocketOptions.Builder.html#connectTimeout-java.time.Duration-)、アプリケーションの要件とワークロードに基づいて設定してください。詳細については、このトピックで後述する「タイムアウト」のセクションを参照してください。

**ClusterClientOption:クラスターモードが有効なクライアントオプション**

接続が失われたときは [AutoReconnect](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterClientOptions.Builder.html#autoReconnect-boolean-) を有効にします。

[CommandTimeout](https://lettuce.io/core/release/api/io/lettuPrce/core/RedisURI.html#getTimeout--) を設定します。詳細については、このトピックで後述する「タイムアウト」のセクションを参照してください。

[NodeFilter](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterClientOptions.Builder.html#nodeFilter-java.util.function.Predicate-) を設定すると、障害が発生したノードをトポロジから除外できます。Lettuce は、「クラスターノード」出力にあるすべてのノード (PFAIL/FAIL ステータスのノードを含む) をクライアントの「パーティション」 (シャードとも呼ばれます) に保存します。クラスタートポロジを作成するプロセスで、すべてのパーティションノードに接続を試みます。障害が発生したノードを追加する Lettuce の動作は、何らかの理由でノードが交換されるときに接続エラー (または警告) を引き起こす可能性があります。

例えば、フェイルオーバーが完了してクラスターが回復プロセスを開始した後、clusterTopology が更新されている間、ダウンしているノードがトポロジから完全に削除されるまで、クラスターバスノードマップにそれが FAIL ノードとして短期間リストされます。この間、Lettuce クライアントはそのノードを正常なノードと見なし、継続的に接続します。そのため、再試行を使い果たすとエラーが発生します。

例:

```
final ClusterClientOptions clusterClientOptions = 
    ClusterClientOptions.builder()
    ... // other options
    .nodeFilter(it -> 
        ! (it.is(RedisClusterNode.NodeFlag.FAIL) 
        || it.is(RedisClusterNode.NodeFlag.EVENTUAL_FAIL) 
        || it.is(RedisClusterNode.NodeFlag.HANDSHAKE)
        || it.is(RedisClusterNode.NodeFlag.NOADDR)))
    .validateClusterNodeMembership(false)
    .build();
redisClusterClient.setOptions(clusterClientOptions);
```

**注記**  
ノードフィルタリングは、DynamicRefreshSources を true に設定して使用するのが最適です。そうしないと、1 つの問題のあるシードノードからトポロジビューを取得すると、一部のシャードのプライマリノードに障害が発生していると見なされ、このプライマリノードは除外され、スロットがカバーされなくなります。(DynamicRefreshSources が true の場合に) 複数のシードノードが存在すると、この問題が発生する可能性が低くなります。これは、新しく昇格したプライマリとのフェイルオーバー後に、少なくとも一部のシードノードでトポロジビューを更新する必要があるためです。

**ClusterTopologyRefreshOptions: クラスターモード対応クライアントのクラスタートポロジ更新を制御するオプション**

**注記**  
クラスターモードが無効なクラスターは、クラスター検出コマンドをサポートしていないため、すべてのクライアントの動的トポロジー検出機能と互換性があるわけではありません。  
ElastiCache で無効になっているクラスターモードは Lettuce の `MasterSlaveTopologyRefresh` と互換性がありません。代わりに、クラスタモードが無効になっている場合は、`StaticMasterReplicaTopologyProvider` を設定し、クラスターの読み取りと書き込みのエンドポイントを提供します。  
クラスターモードが無効なクラスターとの接続の詳細については、「[Valkey または Redis OSS (クラスターモードが無効) クラスターのエンドポイントを検索する (コンソール)](Endpoints.md#Endpoints.Find.Redis)」を参照してください。  
Lettuce の動的トポロジー検出機能を使いたい場合は、既存のクラスターと同じシャード構成でクラスターモードが有効なクラスターを作成できます。ただし、クラスターモードが有効なクラスターでは、高速フェールオーバーをサポートするために、少なくとも 3 つのシャードと 1 つのレプリカを構成することをお勧めします。

[enablePeriodicRefresh](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#enablePeriodicRefresh-java.time.Duration-) を有効にします。これにより、クラスタートポロジを定期的に更新できるため、クライアントは refreshPeriod の間隔 (デフォルト:60 秒) でクラスタートポロジを更新できます。無効にすると、クライアントはクラスターに対してコマンドの実行を試みたときにエラーが発生した場合にのみ、クラスタートポロジを更新します。

このオプションを有効にすると、このジョブをバックグラウンドタスクに追加することで、クラスタートポロジの更新に伴うレイテンシを減らすことができます。トポロジの更新はバックグラウンドジョブで実行されますが、多数のノードがあるクラスターでは多少遅くなる可能性があります。これは、すべてのノードが最新のクラスタービューを取得するためにそれらのビューに対してクエリが実行されているためです。大規模なクラスターを実行する場合は、この時間を長くすることをお勧めします。

[enableAllAdaptiveRefreshTriggers](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#enableAllAdaptiveRefreshTriggers--) を有効にします。これにより、MOVED\$1REDIRECT、ASK\$1REDIRECT、PERSISTENT\$1RECONNECTS、UNCOVERED\$1SLOT、UNKNOWN\$1NODE のすべての[トリガー](https://lettuce.io/core/6.1.6.RELEASE/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.RefreshTrigger.html)を使用する適応型トポロジ更新が可能になります。適応型更新トリガーは、Valkey または Redis OSS クラスター操作中に発生したイベントに基づいてトポロジビューの更新を開始します。このオプションを有効にすると、前述のトリガーのいずれかが発生すると、トポロジがすぐに更新されます。適応型更新トリガーは、イベントが大規模で発生する可能性があるため (更新間のデフォルトタイムアウトは 30)、タイムアウトを使用してレート制限されます。

[closeStaleConnections](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#closeStaleConnections-boolean-) を有効にします。これにより、クラスタートポロジを更新するときに、古い接続を閉じることができます。[ClusterTopologyRefreshOptions.isPeriodicRefreshEnabled()](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.html#isPeriodicRefreshEnabled--) が true の場合にのみ有効になります。有効にすると、クライアントは古い接続を閉じて新しい接続をバックグラウンドで作成できます。これにより、コマンドのランタイムに失敗した接続を処理する必要が減ります。

[dynamicRefreshResources](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#dynamicRefreshSources-boolean-) を有効にします。小規模なクラスターでは DynamicRefreshResources を有効にし、大規模なクラスターでは無効にすることをお勧めします。DynamicRefreshResources を使用すると、提供されたシードノード (クラスター構成エンドポイントなど) からクラスターノードを検出できます。検出されたすべてのノードを、クラスタートポロジを更新するためのソースとして使用します。

動的更新を使用すると、検出されたすべてのノードにクラスタートポロジを照会し、最も正確なクラスタービューを選択しようと試みます。false に設定すると、最初のシードノードのみがトポロジ検出のソースとして使用され、クライアント数は最初のシードノードについてのみ取得されます。無効になっている場合、クラスター設定エンドポイントが障害の発生したノードに解決されたとき、クラスタービューを更新しようとすると失敗し、例外が発生します。このシナリオは、障害が発生したノードのエントリがクラスター設定エンドポイントから削除されるまでに時間がかかるときに発生する可能性があります。そのため、設定エンドポイントは、障害が発生したノードに短期間ランダムに解決できます。

ただし、有効にすると、クラスタービューから受信したすべてのクラスターノードを使用して、現在のビューについてクエリを実行します。障害が発生したノードをそのビューから除外するので、トポロジ更新は成功します。ただし、dynamicRefreshSources が true の場合、Lettuce はすべてのノードにクエリを実行してクラスタービューを取得し、結果を比較します。そのため、多数のノードを持つクラスターではコストがかかる可能性があります。多数のノードがあるクラスターでは、この機能をオフにすることをお勧めします。

```
final ClusterTopologyRefreshOptions topologyOptions = 
    ClusterTopologyRefreshOptions.builder()
    .enableAllAdaptiveRefreshTriggers()
    .enablePeriodicRefresh()
    .dynamicRefreshSources(true)
    .build();
```

**ClientResources**

[DnsResolver](https://lettuce.io/core/release/api/io/lettuce/core/resource/DefaultClientResources.Builder.html#dnsResolver-io.lettuce.core.resource.DnsResolver-) を [DirContextDnsResolver](https://lettuce.io/core/release/api/io/lettuce/core/resource/DirContextDnsResolver.html) で設定します。DNS リゾルバーは Java の com.sun.jndi.dns.DnsContextFactory をベースにしています。

[reconnectDelay](https://lettuce.io/core/release/api/io/lettuce/core/resource/DefaultClientResources.Builder.html#reconnectDelay-io.lettuce.core.resource.Delay-) をエクスポネンシャルバックオフとフルジッターで設定します。Lettuce には、エクスポネンシャルバックオフ戦略に基づく再試行メカニズムが組み込まれています。詳細については、AWS アーキテクチャブログの「[エクスポネンシャルバックオフとジッター](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter)」を参照してください。再試行バックオフ戦略の重要性に関する詳細については、AWS データベースブログの[ベストプラクティスブログ投稿](https://aws.amazon.com/blogs/database/best-practices-redis-clients-and-amazon-elasticache-for-redis/)のバックオフロジックのセクションを参照してください。

```
ClientResources clientResources = DefaultClientResources.builder()
   .dnsResolver(new DirContextDnsResolver())
    .reconnectDelay(
        Delay.fullJitter(
            Duration.ofMillis(100),     // minimum 100 millisecond delay
            Duration.ofSeconds(10),      // maximum 10 second delay
            100, TimeUnit.MILLISECONDS)) // 100 millisecond base
    .build();
```

**Timeouts **

コマンドのタイムアウトよりも低い接続タイムアウト値を使用してください。Lettuce はレイジー接続確立を使用します。そのため、接続タイムアウトがコマンドタイムアウトよりも大きい場合、Lettuce が異常なノードへの接続を試みてコマンドのタイムアウトが常に超過すると、トポロジ更新後に障害が一定期間持続する可能性があります。

異なるコマンドに対しては動的コマンドタイムアウトを使用してください。コマンドの想定期間に基づいてコマンドタイムアウトを設定することをお勧めします。例えば、FLUSHDB、FLUSHALL、KEYS、SMEMBERS、Lua スクリプトなど、複数のキーを反復処理するコマンドにはタイムアウトを長く設定します。SET、GET、HSET など、1 つのキーコマンドではタイムアウトを短くします。

**注記**  
次の例で設定されているタイムアウトは、最大 20 バイトの長さのキーと値で SET/GET コマンドを実行したテスト用です。コマンドが複雑な場合や、キーと値が大きい場合は、処理時間が長くなる可能性があります。タイムアウトは、アプリケーションのユースケースに基づいて設定する必要があります。

```
private static final Duration META_COMMAND_TIMEOUT = Duration.ofMillis(1000);
private static final Duration DEFAULT_COMMAND_TIMEOUT = Duration.ofMillis(250);
// Socket connect timeout should be lower than command timeout for Lettuce
private static final Duration CONNECT_TIMEOUT = Duration.ofMillis(100);
    
SocketOptions socketOptions = SocketOptions.builder()
    .connectTimeout(CONNECT_TIMEOUT)
    .build();
 

class DynamicClusterTimeout extends TimeoutSource {
     private static final Set<ProtocolKeyword> META_COMMAND_TYPES = ImmutableSet.<ProtocolKeyword>builder()
          .add(CommandType.FLUSHDB)
          .add(CommandType.FLUSHALL)
          .add(CommandType.CLUSTER)
          .add(CommandType.INFO)
          .add(CommandType.KEYS)
          .build();

    private final Duration defaultCommandTimeout;
    private final Duration metaCommandTimeout;

    DynamicClusterTimeout(Duration defaultTimeout, Duration metaTimeout)
    {
        defaultCommandTimeout = defaultTimeout;
        metaCommandTimeout = metaTimeout;
    }

    @Override
    public long getTimeout(RedisCommand<?, ?, ?> command) {
        if (META_COMMAND_TYPES.contains(command.getType())) {
            return metaCommandTimeout.toMillis();
        }
        return defaultCommandTimeout.toMillis();
    }
}

// Use a dynamic timeout for commands, to avoid timeouts during
// cluster management and slow operations.
TimeoutOptions timeoutOptions = TimeoutOptions.builder()
.timeoutSource(
    new DynamicClusterTimeout(DEFAULT_COMMAND_TIMEOUT, META_COMMAND_TIMEOUT))
.build();
```

# 例: TLS が有効な場合のクラスターモードの Lettuce の設定
<a name="BestPractices.Clients-lettuce-cme"></a>

**注記**  
次の例のタイムアウトは、最大 20 バイトの長さのキーと値で SET/GET コマンドを実行したテスト用です。コマンドが複雑な場合や、キーと値が大きい場合は、処理時間が長くなる可能性があります。タイムアウトは、アプリケーションのユースケースに基づいて設定する必要があります。

```
// Set DNS cache TTL
public void setJVMProperties() {
    java.security.Security.setProperty("networkaddress.cache.ttl", "10");
}

private static final Duration META_COMMAND_TIMEOUT = Duration.ofMillis(1000);
private static final Duration DEFAULT_COMMAND_TIMEOUT = Duration.ofMillis(250);
// Socket connect timeout should be lower than command timeout for Lettuce
private static final Duration CONNECT_TIMEOUT = Duration.ofMillis(100);

// Create RedisURI from the cluster configuration endpoint
clusterConfigurationEndpoint = <cluster-configuration-endpoint> // TODO: add your cluster configuration endpoint
final RedisURI redisUriCluster =
    RedisURI.Builder.redis(clusterConfigurationEndpoint)
        .withPort(6379)
        .withSsl(true)
        .build();

// Configure the client's resources                
ClientResources clientResources = DefaultClientResources.builder()
    .reconnectDelay(
        Delay.fullJitter(
            Duration.ofMillis(100),     // minimum 100 millisecond delay
            Duration.ofSeconds(10),      // maximum 10 second delay
            100, TimeUnit.MILLISECONDS)) // 100 millisecond base
    .dnsResolver(new DirContextDnsResolver())
    .build(); 

// Create a cluster client instance with the URI and resources
RedisClusterClient redisClusterClient = 
    RedisClusterClient.create(clientResources, redisUriCluster);

// Use a dynamic timeout for commands, to avoid timeouts during
// cluster management and slow operations.
class DynamicClusterTimeout extends TimeoutSource {
     private static final Set<ProtocolKeyword> META_COMMAND_TYPES = ImmutableSet.<ProtocolKeyword>builder()
          .add(CommandType.FLUSHDB)
          .add(CommandType.FLUSHALL)
          .add(CommandType.CLUSTER)
          .add(CommandType.INFO)
          .add(CommandType.KEYS)
          .build();

    private final Duration metaCommandTimeout;
    private final Duration defaultCommandTimeout;

    DynamicClusterTimeout(Duration defaultTimeout, Duration metaTimeout)
    {
        defaultCommandTimeout = defaultTimeout;
        metaCommandTimeout = metaTimeout;
    }

    @Override
    public long getTimeout(RedisCommand<?, ?, ?> command) {
        if (META_COMMAND_TYPES.contains(command.getType())) {
            return metaCommandTimeout.toMillis();
        }
        return defaultCommandTimeout.toMillis();
    }
}

TimeoutOptions timeoutOptions = TimeoutOptions.builder()
    .timeoutSource(new DynamicClusterTimeout(DEFAULT_COMMAND_TIMEOUT, META_COMMAND_TIMEOUT))
     .build();

// Configure the topology refreshment options
final ClusterTopologyRefreshOptions topologyOptions = 
    ClusterTopologyRefreshOptions.builder()
    .enableAllAdaptiveRefreshTriggers()
    .enablePeriodicRefresh()
    .dynamicRefreshSources(true)
    .build();

// Configure the socket options
final SocketOptions socketOptions = 
    SocketOptions.builder()
    .connectTimeout(CONNECT_TIMEOUT) 
    .keepAlive(true)
    .build();

// Configure the client's options
final ClusterClientOptions clusterClientOptions = 
    ClusterClientOptions.builder()
    .topologyRefreshOptions(topologyOptions)
    .socketOptions(socketOptions)
    .autoReconnect(true)
    .timeoutOptions(timeoutOptions) 
    .nodeFilter(it -> 
        ! (it.is(RedisClusterNode.NodeFlag.FAIL) 
        || it.is(RedisClusterNode.NodeFlag.EVENTUAL_FAIL) 
        || it.is(RedisClusterNode.NodeFlag.NOADDR))) 
    .validateClusterNodeMembership(false)
    .build();
    
redisClusterClient.setOptions(clusterClientOptions);

// Get a connection
final StatefulRedisClusterConnection<String, String> connection = 
    redisClusterClient.connect();

// Get cluster sync/async commands   
RedisAdvancedClusterCommands<String, String> sync = connection.sync();
RedisAdvancedClusterAsyncCommands<String, String> async = connection.async();
```

# 例: TLS が有効でクラスターモードが無効な場合の Lettuce の設定
<a name="BestPractices.Clients-lettuce-cmd"></a>

**注記**  
次の例のタイムアウトは、最大 20 バイトの長さのキーと値で SET/GET コマンドを実行したテスト用です。コマンドが複雑な場合や、キーと値が大きい場合は、処理時間が長くなる可能性があります。タイムアウトは、アプリケーションのユースケースに基づいて設定する必要があります。

```
// Set DNS cache TTL
public void setJVMProperties() {
    java.security.Security.setProperty("networkaddress.cache.ttl", "10");
}

private static final Duration META_COMMAND_TIMEOUT = Duration.ofMillis(1000);
private static final Duration DEFAULT_COMMAND_TIMEOUT = Duration.ofMillis(250);
// Socket connect timeout should be lower than command timeout for Lettuce
private static final Duration CONNECT_TIMEOUT = Duration.ofMillis(100);

// Create RedisURI from the primary/reader endpoint
clusterEndpoint = <primary/reader-endpoint> // TODO: add your node endpoint
RedisURI redisUriStandalone =
    RedisURI.Builder.redis(clusterEndpoint).withPort(6379).withSsl(true).withDatabase(0).build();

ClientResources clientResources =
    DefaultClientResources.builder()
        .dnsResolver(new DirContextDnsResolver())
        .reconnectDelay(
            Delay.fullJitter(
                Duration.ofMillis(100), // minimum 100 millisecond delay
                Duration.ofSeconds(10), // maximum 10 second delay
                100,
                TimeUnit.MILLISECONDS)) // 100 millisecond base
        .build();

// Use a dynamic timeout for commands, to avoid timeouts during
// slow operations.
class DynamicTimeout extends TimeoutSource {
     private static final Set<ProtocolKeyword> META_COMMAND_TYPES = ImmutableSet.<ProtocolKeyword>builder()
          .add(CommandType.FLUSHDB)
          .add(CommandType.FLUSHALL)
          .add(CommandType.INFO)
          .add(CommandType.KEYS)
          .build();

    private final Duration metaCommandTimeout;
    private final Duration defaultCommandTimeout;

    DynamicTimeout(Duration defaultTimeout, Duration metaTimeout)
    {
        defaultCommandTimeout = defaultTimeout;
        metaCommandTimeout = metaTimeout;
    }

    @Override
    public long getTimeout(RedisCommand<?, ?, ?> command) {
        if (META_COMMAND_TYPES.contains(command.getType())) {
            return metaCommandTimeout.toMillis();
        }
        return defaultCommandTimeout.toMillis();
    }
}

TimeoutOptions timeoutOptions = TimeoutOptions.builder()
    .timeoutSource(new DynamicTimeout(DEFAULT_COMMAND_TIMEOUT, META_COMMAND_TIMEOUT))
     .build();                      
                                    
final SocketOptions socketOptions =
    SocketOptions.builder().connectTimeout(CONNECT_TIMEOUT).keepAlive(true).build();

ClientOptions clientOptions =
    ClientOptions.builder().timeoutOptions(timeoutOptions).socketOptions(socketOptions).build();

RedisClient redisClient = RedisClient.create(clientResources, redisUriStandalone);
redisClient.setOptions(clientOptions);
```

## デュアルスタッククラスターの優先プロトコルの設定 (Valkey および Redis OSS)
<a name="network-type-configuring-dual-stack-redis"></a>

クラスターモードが有効な Valkey または Redis クラスターでは、IP 検出パラメータを使用して、クライアントがクラスター内のノードに接続するために使用するプロトコルを制御できます。IP 検出パラメータは、IPv4 または IPv6 に設定できます。

Valkey または Redis クラスターの場合、IP 検出パラメータは、[クラスタースロット ()](https://valkey.io/commands/cluster-slots/)、[クラスターシャード ()](https://valkey.io/commands/cluster-shards/)、[クラスターノード ()](https://valkey.io/commands/cluster-nodes/) の出力で使用される IP プロトコルを設定します。これらのコマンドは、クライアントがクラスタートポロジを検出するために使用されます。クライアントは、これらのコマンドの IP を使用して、クラスター内の他のノードに接続します。

IP 検出を変更しても、接続しているクライアントのダウンタイムは発生しません。ただし、変更が反映されるまで時間がかかる場合があります。Valkey または Redis クラスターに変更が完全に伝播されたかどうかを判断するには、`cluster slots` の出力をモニタリングします。cluster slots コマンドによって返されたすべてのノードが新しいプロトコルの IP を報告すると、変更の反映が完了します。

Redis-Py を使った例:

```
cluster = RedisCluster(host="xxxx", port=6379)
target_type = IPv6Address # Or IPv4Address if changing to IPv4

nodes = set()
while len(nodes) == 0 or not all((type(ip_address(host)) is target_type) for host in nodes):
    nodes = set()

   # This refreshes the cluster topology and will discovery any node updates.
   # Under the hood it calls cluster slots
    cluster.nodes_manager.initialize()
    for node in cluster.get_nodes():
        nodes.add(node.host)
    self.logger.info(nodes)

    time.sleep(1)
```

Lettuce の例:

```
RedisClusterClient clusterClient = RedisClusterClient.create(RedisURI.create("xxxx", 6379));

Class targetProtocolType = Inet6Address.class; // Or Inet4Address.class if you're switching to IPv4

Set<String> nodes;
    
do {
   // Check for any changes in the cluster topology.
   // Under the hood this calls cluster slots
    clusterClient.refreshPartitions();
    Set<String> nodes = new HashSet<>();

    for (RedisClusterNode node : clusterClient.getPartitions().getPartitions()) {
        nodes.add(node.getUri().getHost());
    }

    Thread.sleep(1000);
} while (!nodes.stream().allMatch(node -> {
            try {
                return finalTargetProtocolType.isInstance(InetAddress.getByName(node));
            } catch (UnknownHostException ignored) {}
            return false;
}));
```