

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

# Prácticas recomendadas para clientes (Valkey y Redis OSS)
<a name="BestPractices.Clients.redis"></a>

Descubra las prácticas recomendadas en escenarios habituales y siga los ejemplos de código de algunas de las bibliotecas cliente de Valkey y Redis OSS de código abierto más populares (redis-py, PHPRedis y Lettuce), así como las prácticas recomendadas para interactuar con los recursos de ElastiCache con bibliotecas cliente de Memcached de código abierto de uso habitual.

**Topics**
+ [

# Gran cantidad de conexiones (Valkey y Redis OSS)
](BestPractices.Clients.Redis.Connections.md)
+ [

# Detección de clústeres por parte del cliente y retroceso exponencial (Valkey y Redis OSS)
](BestPractices.Clients.Redis.Discovery.md)
+ [

# Configuración de un tiempo de espera del cliente (Valkey y Redis OSS)
](BestPractices.Clients.Redis.ClientTimeout.md)
+ [

# Configuración de un tiempo de espera del servidor (Valkey y Redis OSS)
](BestPractices.Clients.Redis.ServerTimeout.md)
+ [

# Scripts de Lua
](BestPractices.Clients.Redis.LuaScripts.md)
+ [

# Almacenamiento de elementos compuestos de gran tamaño (Valkey y Redis OSS)
](BestPractices.Clients.Redis.LargeItems.md)
+ [

# Configuración del cliente de Lettuce (Valkey y Redis OSS)
](BestPractices.Clients-lettuce.md)
+ [

## Configuración de un protocolo preferido para clústeres de doble pila (Valkey y Redis OSS)
](#network-type-configuring-dual-stack-redis)

# Gran cantidad de conexiones (Valkey y Redis OSS)
<a name="BestPractices.Clients.Redis.Connections"></a>

Las cachés sin servidor y los nodos individuales de ElastiCache para Redis OSS admiten hasta 65 000 conexiones de clientes simultáneas. Sin embargo, para optimizar el rendimiento, recomendamos que las aplicaciones cliente no funcionen constantemente con ese volumen de conexiones. Valkey y Redis OSS tienen cada uno un subproceso basado en un bucle de eventos en el que las solicitudes entrantes de los clientes se gestionan de forma secuencial. Esto significa que el tiempo de respuesta de un cliente determinado se hace más largo a medida que aumenta el número de clientes conectados.

Puede tomar las siguientes medidas para evitar un cuello de botella en la conexión del servidor de Valkey o Redis OSS:
+ Llevar a cabo las operaciones de lectura a partir de réplicas de lectura. Puede hacer esto usando los puntos de conexión del lector ElastiCache con el modo de clúster deshabilitado o usando réplicas para las lecturas con el modo de clúster habilitado, lo que incluye una caché sin servidor.
+ Distribuir el tráfico de escritura entre varios nodos principales. Puede hacerlo de dos formas. Puede utilizar un clúster de Valkey y Redis OSS con varias particiones en un cliente compatible con el modo de clúster. También puede escribir en varios nodos principales con el modo de clúster deshabilitado y con partición en el lado del cliente. Esto se hace automáticamente en una memoria caché sin servidor.
+ Usar un grupo de conexiones cuando esté disponible en la biblioteca de su cliente.

En general, crear una conexión de TCP es una operación costosa desde el punto de vista computacional en comparación con los comandos típicos de Valkey o Redis OSS. Por ejemplo, gestionar una solicitud SET/GET es una orden de magnitud más rápido cuando se reutiliza una conexión existente. El uso de un grupo de conexiones de clientes con un tamaño finito reduce la sobrecarga en la administración de conexiones. También limita el número de conexiones entrantes simultáneas desde la aplicación cliente.

El siguiente ejemplo de código de PHPRedis muestra cómo se crea una nueva conexión para cada nueva solicitud de usuario:

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

Hemos comparado este código en bucle en una instancia de Amazon Elastic Compute Cloud (Amazon EC2) conectada a un nodo ElastiCache para Redis OSS de Graviton2 (m6g.2xlarge). Hemos colocado el cliente y el servidor dentro de la misma zona de disponibilidad. La latencia media de toda la operación fue de 2,82 milisegundos.

Cuando actualizamos el código y utilizamos conexiones persistentes y un grupo de conexiones, la latencia media de toda la operación fue de 0,21 milisegundos:

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

Configuraciones de redis.ini obligatorias:
+ `redis.pconnect.pooling_enabled=1`
+ `redis.pconnect.connection_limit=10`

El siguiente código es un ejemplo de un [grupo de conexiones Redis-py](https://redis.readthedocs.io/en/stable/):

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

El siguiente código es un ejemplo de un [grupo de conexiones 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);
}
```

# Detección de clústeres por parte del cliente y retroceso exponencial (Valkey y Redis OSS)
<a name="BestPractices.Clients.Redis.Discovery"></a>

Al conectarse a un clúster de ElastiCache Valkey o Redis OSS con el modo de clúster habilitado, la biblioteca cliente correspondiente debe reconocer los clústeres. Los clientes deben obtener un mapa de los slots hash de los nodos correspondientes del clúster para poder enviar las solicitudes a los nodos correctos, y evitar así la sobrecarga de rendimiento que supone gestionar las redirecciones del clúster. Como resultado, el cliente debe descubrir (o detectar) una lista completa de los slots y los nodos mapeados en dos situaciones diferentes:
+ El cliente se inicializa y debe completar la configuración inicial de los slots.
+ Se recibe una redirección MOVED del servidor; por ejemplo, en una conmutación por error, cuando la réplica se hace cargo de todos los slots atendidos por el nodo principal anterior, o en una nueva partición, cuando los slots se mueven del nodo principal de origen al nodo principal de destino.

Por lo general, el proceso de detección por parte del cliente se lleva a cabo enviando un comando CLUSTER SLOT o CLUSTER NODE al servidor de Valkey o Redis OSS. Recomendamos el método CLUSTER SLOT porque devuelve al cliente el conjunto de rangos de slots, así como los nodos principales y de réplica asociados. Es un método que no requiere un análisis adicional por parte del cliente y es más eficaz.

En función de la topología del clúster, el tamaño de la respuesta al comando CLUSTER SLOT puede variar según el tamaño del clúster. Los clústeres más grandes y con más nodos producen una respuesta mayor. Por lo tanto, es importante asegurarse de que la cantidad de clientes que llevan a cabo la detección de la topología del clúster no aumente de forma ilimitada. Por ejemplo, cuando la aplicación cliente se inicia o pierde la conexión con el servidor y debe realizar una detección de clústeres, un error común es que la aplicación cliente desencadene varias solicitudes de reconexión y detección sin añadir un retroceso exponencial con el nuevo intento. Esto puede hacer que el servidor de Valkey o Redis OSS deje de responder durante mucho tiempo, con un uso de la CPU del 100 %. La interrupción se prolonga si cada comando CLUSTER SLOT debe procesar una gran cantidad de nodos en el bus del clúster. En el pasado, hemos observado varias interrupciones en los clientes debido a este comportamiento en varios lenguajes diferentes, como Python (redis-py-cluster) y Java (Lettuce y Redisson).

En una caché sin servidor, muchos de los problemas se mitigan automáticamente porque la topología de clúster expuesta es estática y consta de dos entradas: un punto de conexión de escritura y otro de lectura. La detección de clústeres también se distribuye automáticamente entre varios nodos cuando se utiliza el punto de conexión de la caché. Sin embargo, las siguientes recomendaciones siguen siendo útiles.

A fin de mitigar el impacto causado por una entrada repentina de solicitudes de conexión y detección, recomendamos lo siguiente:
+ Implemente un grupo de conexiones de cliente con un tamaño finito a fin de limitar el número de conexiones entrantes simultáneas desde la aplicación cliente.
+ Cuando el cliente se desconecte del servidor debido al tiempo de espera, vuelva a intentarlo con retroceso exponencial y fluctuación. Esto evitará que varios clientes sobrecarguen el servidor al mismo tiempo.
+ Utilice la guía en [Búsqueda de puntos de conexión en ElastiCache](Endpoints.md) para encontrar el punto de conexión del clúster que necesitará a fin de realizar la detección del clúster. De este modo, distribuirá la carga de detección entre todos los nodos del clúster (hasta 90), en lugar de centrarse en unos pocos nodos raíz codificados del clúster.

A continuación, se muestran algunos ejemplos de código para la lógica de reintentos de retroceso exponencial en redis-py, PHPRedis y Lettuce.

**Ejemplo 1 de lógica de retroceso: redis-py**

Redis-py tiene un mecanismo de reintento incorporado: se lleva a cabo un reintento inmediatamente después de un error. Este mecanismo se puede activar mediante el argumento `retry_on_timeout` proporcionado al crear un objeto de [Redis OSS](https://redis.readthedocs.io/en/stable/examples/connection_examples.html#redis.Redis). Aquí mostramos un mecanismo de reintento personalizado con retroceso exponencial y fluctuación. Hemos enviado una solicitud de extracción para implementar de forma nativa el retroceso exponencial en [redis-py (1494)](https://github.com/andymccurdy/redis-py/pull/1494). En el futuro, puede que no sea necesario implementarlo manualmente.

```
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
```

Luego, puede utilizar el siguiente código para establecer un valor:

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

En función de la carga de trabajo, es posible que desee cambiar el valor del retroceso base (de 1 segundo a unas pocas decenas o cientos de milisegundos) para las cargas de trabajo sensibles a la latencia.

**Ejemplo 2 de lógica de retroceso: PHPRedis**

PHPRedis tiene un mecanismo de reintento incorporado: hay un número máximo de reintento de 10 veces (no configurable). Hay un retraso configurable entre los intentos (con una fluctuación a partir del segundo reintento). Para obtener más información, consulte el siguiente [código de muestra](https://github.com/phpredis/phpredis/blob/b0b9dd78ef7c15af936144c1b17df1a9273d72ab/library.c#L335-L368). Enviamos una solicitud de extracción para implementar de forma nativa el retroceso exponencial en [PHPredis (1986)](https://github.com/phpredis/phpredis/pull/1986); desde entonces, se ha fusionado y [documentado](https://github.com/phpredis/phpredis/blob/develop/README.md#retry-and-backoff). Si utiliza la última versión de PHPRedis, no será necesario implementarla manualmente, pero hemos incluido aquí la referencia para las versiones anteriores. De momento, aquí tiene un ejemplo de código para configurar el retraso del mecanismo de reintento:

```
$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);
```

**Ejemplo 3 de lógica de retroceso: Lettuce**

Lettuce tiene mecanismos de reintento incorporados que emplean las estrategias de retroceso exponencial descritas en la publicación [Exponential Backoff and Jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/). A continuación, puede ver un fragmento de código con el método de fluctuación total:

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

	}
}
```

# Configuración de un tiempo de espera del cliente (Valkey y Redis OSS)
<a name="BestPractices.Clients.Redis.ClientTimeout"></a>

**Configuración del tiempo de espera del cliente**

Configure el tiempo de espera del lado del cliente de la manera adecuada, a fin de que el servidor tenga tiempo suficiente para procesar la solicitud y generar la respuesta. Esto también le permitirá responder rápido a los errores si no se puede establecer la conexión con el servidor. Ciertos comandos de Valkey o Redis OSS pueden ser más costosos desde el punto de vista computacional que otros. Un ejemplo de esto son los scripts de Lua o las transacciones MULTI/EXEC con varios comandos que deben ejecutarse de forma integral. Por lo general, es recomendable aumentar el tiempo de espera del cliente para evitar que este se quede sin tiempo antes de recibir la respuesta del servidor, lo que incluye lo siguiente:
+ Ejecutar comandos en varias claves
+ Ejecutar transacciones MULTI/EXEC o scripts de Lua que consten de varios comandos individuales de Valkey o Redis OSS
+ Leer valores grandes
+ Realizar operaciones de bloqueo, como BLPOP

En el caso de una operación de bloqueo como BLPOP, el procedimiento recomendado es establecer el tiempo de espera del comando en un número inferior al tiempo de espera del socket.

A continuación, encontrará ejemplos de código para implementar un tiempo de espera del lado del cliente en redis-py, PHPRedis y Lettuce.

**Ejemplo 1 de configuración de tiempo de espera: redis-py**

A continuación, puede ver un ejemplo de código con 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)
```

**Ejemplo 2 de configuración de tiempo de espera: PHPRedis**

A continuación, puede ver un ejemplo de código con 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
```

**Ejemplo 3 de configuración de tiempo de espera: Lettuce**

A continuación, puede ver un ejemplo de código con 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();
		}
	}
}
```

# Configuración de un tiempo de espera del servidor (Valkey y Redis OSS)
<a name="BestPractices.Clients.Redis.ServerTimeout"></a>

Hemos observado casos en los que la aplicación de un cliente tiene un gran número de clientes inactivos conectados, pero no envía comandos de forma activa. En estos casos, se pueden agotar las 65 000 conexiones con un número elevado de clientes inactivos. Para evitar este tipo de situaciones, configure el tiempo de espera adecuadamente en el servidor mediante [Parámetros de Valkey y Redis OSS](ParameterGroups.Engine.md#ParameterGroups.Redis). Esto garantiza que el servidor tome la iniciativa en desconectar a los clientes inactivos para evitar un aumento en el número de conexiones. Esta configuración no está disponible en las caché sin servidor.

# Scripts de Lua
<a name="BestPractices.Clients.Redis.LuaScripts"></a>

Valkey y Redis OSS admiten más de 200 comandos, incluidos los que ejecutan scripts de Lua. Sin embargo, en lo que respecta a los scripts de Lua, hay varios inconvenientes que pueden afectar a la memoria y a la disponibilidad de Valkey o Redis OSS.

**Scripts de Lua no parametrizados**

Cada script de Lua se almacena en caché en el servidor de Valkey o Redis OSS antes de ejecutarse. Los scripts de Lua no parametrizados son únicos, lo que puede provocar que el servidor de Valkey o Redis OSS almacene una gran cantidad de ellos y consuma más memoria. Para mitigar esta situación, asegúrese de que todos los scripts de Lua estén parametrizados y ejecute SCRIPT FLUSH con regularidad a fin de depurar los scripts de Lua almacenados en caché si es necesario.

También tenga en cuenta que se deben proporcionar claves. Si no se proporciona ningún valor para el parámetro KEY, el script fallará. Por ejemplo, lo siguiente no funcionará: 

```
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.
```

Esto sí funcionará:

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

En el siguiente ejemplo, se muestra cómo definir y utilizar parámetros: En primer lugar, tenemos el ejemplo de un método no parametrizado que da como resultado tres scripts de Lua en caché diferentes (no recomendable):

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

En lugar de esto, utilice el siguiente patrón para crear un único script que pueda aceptar los parámetros pasados:

```
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
```

**Scripts de Lua de larga duración**

Los scripts de Lua pueden ejecutar varios comandos de forma atómica, por lo que pueden tardar más en completarse que un comando normal de Valkey o Redis OSS. Si el script de Lua solo ejecuta operaciones de solo lectura, puede detenerlo durante la ejecución. Sin embargo, cuando el script de Lua lleve a cabo operaciones de escritura, no se puede detener y debe ejecutarse hasta el final. Un script de Lua de ejecución prolongada que esté mutando puede provocar que el servidor de Valkey o Redis OSS deje de responder durante mucho tiempo. Para mitigar este problema, evite el uso de scripts de Lua de larga duración y pruébelos en un entorno de preproducción.

**Script de Lua con escrituras encubiertas**

Aunque Valkey o Redis OSS hayan superado la `maxmemory`, un script de Lua puede seguir escribiendo nuevos datos en Valkey o Redis OSS de varias maneras:
+ El script se inicia cuando el servidor de Valkey o Redis OSS está por debajo de `maxmemory` y contiene varias operaciones de escritura en su interior.
+ El primer comando de escritura del script no consume memoria (como DEL) y va seguido de más operaciones de escritura que sí consumen memoria.
+ Puede mitigar este problema configurando una política de expulsión adecuada en un servidor de Valkey o Redis OSS que no sea `noeviction`. Esto permite que Redis OSS expulse elementos y libere memoria entre los scripts de Lua.

# Almacenamiento de elementos compuestos de gran tamaño (Valkey y Redis OSS)
<a name="BestPractices.Clients.Redis.LargeItems"></a>

En algunos casos, una aplicación puede almacenar elementos compuestos de gran tamaño en Valkey o Redis OSS (como un conjunto de datos hash de varios GB). Esto no es recomendable, ya que suele provocar problemas de rendimiento en Valkey o Redis OSS. Por ejemplo, el cliente puede ejecutar un comando HGETALL para recuperar toda la colección de hash de varios GB. Se puede generar una presión en la memoria importante para el servidor de Valkey o Redis OSS que almacena el elemento de gran tamaño en el búfer de salida del cliente. Además, para la migración de slots en modo clúster, ElastiCache no migra los slots que contienen elementos con un tamaño serializado superior a 256 MB.

Para resolver los problemas implicados en la gestión de elementos de gran tamaño, recomendamos lo siguiente:
+ Divida el elemento compuesto grande en varios elementos más pequeños. Algo que puede hacer es dividir una colección hash grande en campos clave-valor individuales con un esquema de nombres de clave que refleje adecuadamente la colección; por ejemplo, puede utilizar un prefijo común en el nombre de la clave para identificar la colección de elementos. Si debe acceder a varios campos de la misma colección de forma integral, puede usar el comando MGET para recuperar varios valores clave en el mismo comando.
+ Si ha evaluado todas las opciones y sigue sin poder dividir el conjunto de datos de gran tamaño de la colección, intente utilizar comandos que funcionen en un subconjunto de los datos de la colección, en lugar de en toda la colección. Evite los casos de uso que requieran recuperar de forma integral toda la colección de varios GB con el mismo comando. Un ejemplo de esto es el uso de los comandos HGET o HMGET, en lugar de HGETALL, en las colecciones de hash.

# Configuración del cliente de Lettuce (Valkey y Redis OSS)
<a name="BestPractices.Clients-lettuce"></a>

En esta sección se describen las opciones de configuración recomendadas de Java y Lettuce y cómo se aplican a los clústeres de ElastiCache.

Las recomendaciones de esta sección se probaron con la versión 6.2.2 de Lettuce.

**Topics**
+ [

# Ejemplo: Configuración de Lettuce para el modo de clúster con TLS habilitado
](BestPractices.Clients-lettuce-cme.md)
+ [

# Ejemplo: Configuración de Lettuce para el modo de clúster deshabilitado con TLS habilitado
](BestPractices.Clients-lettuce-cmd.md)

**TTL de la caché DNS de Java**

La máquina virtual de Java (JVM) almacena en caché las búsquedas de nombres DNS. Cuando la JVM resuelve un nombre de host en una dirección IP, almacena en caché la dirección IP durante un periodo de tiempo especificado, conocido como *tiempo de vida* (TTL).

La elección del valor de TTL implica un compromiso entre latencia y la capacidad de respuesta a los cambios. Con TTL más cortos, los solucionadores de DNS detectan las actualizaciones en el DNS del clúster con mayor rapidez. Esto puede hacer que la aplicación responda más rápido a las sustituciones u otros flujos de trabajo a los que se somete el clúster. Sin embargo, si el TTL es demasiado bajo, aumenta el volumen de consultas, lo que puede aumentar la latencia de la aplicación. Aunque no existe un valor de TTL correcto, vale la pena esperar a que surta efecto un cambio cuando se configura el valor de TTL.

Como los nodos de ElastiCache utilizan entradas de nombres de DNS que pueden cambiar de vez en cuando, recomendamos que configure JVM con un TTL bajo de 5 a 10 segundos. Con esto, se asegurará de que, cuando cambie la dirección IP de un nodo, su aplicación pueda recibir y utilizar la nueva dirección IP del recurso volviendo a consultar la entrada de DNS.

En algunas configuraciones de Java, el TTL predeterminado de JVM está establecido de forma que nunca se actualicen las entradas DNS hasta que se reinicie la JVM.

Para obtener más información sobre cómo configurar el TTL de JVM, consulte [Cómo configurar el TTL de JVM](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-jvm-ttl.html#how-to-set-the-jvm-ttl).

**Versión de Lettuce**

Recomendamos usar la versión 6.2.2 o posterior de Lettuce.

**puntos de conexión**

Cuando utilice clústeres habilitados para el modo de clúster, establezca `redisUri` en el punto de conexión de configuración del clúster. La búsqueda de DNS para este URI devuelve una lista de todos los nodos disponibles en el clúster y se resuelve aleatoriamente en uno de ellos durante la inicialización del clúster. Para obtener más información sobre cómo funciona la actualización de topología, consulte *dynamicRefreshResources* más adelante en este tema.

**SocketOption**

Habilite [KeepAlive](https://lettuce.io/core/release/api/io/lettuce/core/SocketOptions.KeepAliveOptions.html). Al habilitar esta opción, se reduce la necesidad de gestionar las conexiones erróneas durante el tiempo de ejecución del comando.

Asegúrese de configurar el [Tiempo de espera de la conexión](https://lettuce.io/core/release/api/io/lettuce/core/SocketOptions.Builder.html#connectTimeout-java.time.Duration-) en función de los requisitos de la aplicación y la carga de trabajo. Para obtener más información, consulte la sección de tiempos de espera más adelante en este tema.

**ClusterClientOption: Opciones de cliente habilitadas para el modo de clúster**

Habilite [AutoReconnect](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterClientOptions.Builder.html#autoReconnect-boolean-) cuando se pierda la conexión.

Establezca [CommandTimeout](https://lettuce.io/core/release/api/io/lettuPrce/core/RedisURI.html#getTimeout--). Para obtener más información, consulte la sección Tiempos de espera más adelante en este tema.

Establezca [nodeFilter](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterClientOptions.Builder.html#nodeFilter-java.util.function.Predicate-) para filtrar los nodos con errores de la topología. Lettuce guarda todos los nodos que se encuentran en la salida de los “nodos del clúster” (incluidos los nodos con el estado PFAIL/FAIL) en las “particiones” del cliente. Durante el proceso de creación de la topología del clúster, intenta conectarse a todos los nodos de partición. Este comportamiento de Lettuce de agregar nodos con errores puede provocar errores de conexión (o advertencias) cuando los nodos se sustituyen por cualquier motivo. 

Por ejemplo, una vez que finaliza una conmutación por error y el clúster inicia el proceso de recuperación, mientras se actualiza la topología del clúster, el mapa de nodos del bus del clúster tiene un breve periodo de tiempo en el que el nodo inactivo se muestra como nodo FAIL, antes de que se elimine por completo de la topología. Durante este periodo, el cliente de Lettuce lo considera un nodo en buen estado y se conecta continuamente a él. Esto provoca un error cuando se agota el reintento. 

Por ejemplo:

```
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);
```

**nota**  
El filtrado de nodos se utiliza mejor con DynamicRefreshSources configurado en verdadero. De lo contrario, si la vista de topología se toma de un solo nodo raíz problemático, que ve que un nodo principal de alguna partición está produciendo un error, filtrará este nodo principal, lo que hará que las ranuras no queden cubiertas. Tener varios nodos raíz (cuando DynamicRefreshSources es verdadero) reduce la probabilidad de que se produzca este problema, ya que al menos algunos de los nodos raíz deberían tener una vista de topología actualizada después de una conmutación por error con el nodo principal recién promocionado.

**ClusterTopologyRefreshOptions: opciones para controlar la actualización de la topología del clúster del cliente habilitado para el modo de clúster**

**nota**  
Los clústeres desactivados en modo de clúster no admiten los comandos de detección de clústeres y no son compatibles con la funcionalidad de detección de topología dinámica de todos los clientes.  
El modo de clúster desactivado con ElastiCache no es compatible con `MasterSlaveTopologyRefresh` de Lettuce. En cambio, para el modo de clúster desactivado puede configurar `StaticMasterReplicaTopologyProvider` y proporcionar los puntos de conexión de lectura y escritura del clúster.  
Para obtener más información acerca de la conexión a clústeres desactivados en modo de clúster, consulte [Búsqueda de puntos de conexión de un clúster de Valkey o Redis OSS (modo de clúster deshabilitado) (consola)](Endpoints.md#Endpoints.Find.Redis).  
Si desea utilizar la funcionalidad de detección de topología dinámica de Lettuce, puede crear un clúster habilitado para el modo de clúster con la configuración del mismo fragmento que el clúster existente. Sin embargo, para los clústeres habilitados para el modo de clúster, recomendamos configurar al menos 3 particiones con al menos 1 réplica para admitir una conmutación por error rápida.

Habilite [enablePeriodicRefresh](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#enablePeriodicRefresh-java.time.Duration-). Esto permite las actualizaciones periódicas de la topología del clúster para que el cliente actualice la topología del clúster en los intervalos del periodo de actualización (predeterminado: 60 segundos). Cuando están desactivadas, el cliente actualiza la topología del clúster solo cuando se producen errores al intentar ejecutar comandos en el clúster. 

Con esta opción habilitada, puede reducir la latencia asociada a la actualización de la topología del clúster agregando este trabajo a una tarea que se esté ejecutando en segundo plano. Aunque la actualización de la topología se realiza en un trabajo que se esté ejecutando en segundo plano, puede resultar algo lenta para los clústeres con muchos nodos. Esto se debe a que se están consultando las vistas de todos los nodos para obtener la vista de clúster más actualizada. Si ejecuta un clúster grande, es posible que desee aumentar el periodo.

Habilite [enableAllAdaptiveRefreshTriggers](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#enableAllAdaptiveRefreshTriggers--). Esto permite actualizar la topología adaptativa y utilizar todos los [disparadores](https://lettuce.io/core/6.1.6.RELEASE/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.RefreshTrigger.html): MOVED\$1REDIRECT, ASK\$1REDIRECT, PERSISTENT\$1RECONNECTS, UNCOVERED\$1SLOT, UNKNOWN\$1NODE. Los desencadenadores de actualización adaptativa inician las actualizaciones de la vista de topología en función de los eventos que se producen durante las operaciones de clúster de Valkey y Redis OSS. Al habilitar esta opción, se actualiza inmediatamente la topología cuando se activa uno de los desencadenadores anteriores. Las actualizaciones desencadenadas adaptativas tienen un límite de velocidad mediante un tiempo de espera porque los eventos se pueden producir a gran escala (tiempo de espera predeterminado entre actualizaciones: 30).

Habilite [closeStaleConnections](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#closeStaleConnections-boolean-). Esto permite cerrar las conexiones obsoletas al actualizar la topología del clúster. Solo entra en vigor si [ClusterTopologyRefreshOptions.isPeriodicRefreshEnabled()](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.html#isPeriodicRefreshEnabled--) es verdadero. Cuando se habilita, el cliente puede cerrar las conexiones obsoletas y crear otras nuevas en segundo plano. Esto reduce la necesidad de gestionar las conexiones erróneas durante el tiempo de ejecución del comando.

Habilite [dynamicRefreshResources](https://lettuce.io/core/release/api/io/lettuce/core/cluster/ClusterTopologyRefreshOptions.Builder.html#dynamicRefreshSources-boolean-). Recomendamos habilitar dynamicRefreshResources para clústeres pequeños y desactivarlo para clústeres grandes. dynamicRefreshResources permite detectar los nodos del clúster desde el nodo raíz proporcionado (por ejemplo, el punto de conexión de configuración del clúster). Utiliza todos los nodos detectados como orígenes para actualizar la topología del clúster. 

El uso de la actualización dinámica consulta todos los nodos detectados para la topología del clúster e intenta elegir la vista de clúster más precisa. Si se establece en falso, solo se utilizan los nodos raíz iniciales como orígenes para la detección de la topología y el número de clientes se obtiene solo para los nodos raíz iniciales. Cuando se desactiva, si el punto de conexión de la configuración del clúster se resuelve como un nodo con errores, se produce un error al intentar actualizar la vista del clúster y se producen excepciones. Este escenario se puede producir porque pasa algún tiempo hasta que la entrada de un nodo con errores se elimina del punto de conexión de la configuración del clúster. Por lo tanto, el punto de conexión de la configuración aún se puede resolver aleatoriamente en un nodo erróneo durante un breve periodo de tiempo. 

Sin embargo, cuando se habilita, utilizamos todos los nodos del clúster que se reciben de la vista de clústeres para consultar la vista actual. Como filtramos los nodos con errores de esa vista, la actualización de la topología se realizará correctamente. Sin embargo, cuando dynamicRefreshSources es verdadero, Lettuce consulta todos los nodos para obtener la vista del clúster y, a continuación, compara los resultados. Por lo tanto, puede resultar caro para los clústeres con muchos nodos. Le sugerimos que desactive esta característica para los clústeres con muchos nodos. 

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

**ClientResources**

Configure [DnsResolver](https://lettuce.io/core/release/api/io/lettuce/core/resource/DefaultClientResources.Builder.html#dnsResolver-io.lettuce.core.resource.DnsResolver-) con [DirContextDnsResolver](https://lettuce.io/core/release/api/io/lettuce/core/resource/DirContextDnsResolver.html). El servicio de resolución de DNS se basa en com.sun.jndi.dns.DnsContextFactory de Java.

Configure [reconnectDelay](https://lettuce.io/core/release/api/io/lettuce/core/resource/DefaultClientResources.Builder.html#reconnectDelay-io.lettuce.core.resource.Delay-) con retroceso exponencial y fluctuación total. Lettuce tiene mecanismos de reintento integrados basados en las estrategias de retroceso exponencial. Para obtener más información, consulte [Retroceso exponencial y fluctuación](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter) en el blog de arquitectura de AWS. Para obtener más información sobre la importancia de contar con una estrategia de reintento, consulte las secciones sobre lógica de retraso de la [Publicación del blog sobre mejores prácticas](https://aws.amazon.com/blogs/database/best-practices-redis-clients-and-amazon-elasticache-for-redis/) en el blog de base de datos de AWS.

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

**Tiempos de espera**

Utilice un valor de tiempo de espera de conexión inferior al tiempo de espera del comando. Lettuce utiliza un establecimiento de conexión diferida. Por lo tanto, si el tiempo de espera de conexión es superior al tiempo de espera del comando, puede producirse un periodo de error persistente tras una actualización de la topología si Lettuce intenta conectarse a un nodo en mal estado y siempre se supera el tiempo de espera del comando. 

Utilice un tiempo de espera de comando dinámico para diferentes comandos. Le recomendamos que establezca el tiempo de espera del comando en función de la duración esperada del comando. Por ejemplo, utilice un tiempo de espera más largo para los comandos que se repiten en varias claves, como los scripts FLUSHDB, FLUSHALL, KEYS, SMEMBERS o Lua. Utilice tiempos de espera más cortos para los comandos de una sola clave, como SET, GET y HSET.

**nota**  
Los tiempos de espera que se configuran en el siguiente ejemplo son para pruebas que ejecutaron comandos SET/GET con claves y valores de hasta 20 bytes de longitud. El tiempo de procesamiento puede ser mayor cuando los comandos son complejos o las claves y los valores son más grandes. Debe establecer los tiempos de espera en función del caso de uso de la aplicación. 

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

# Ejemplo: Configuración de Lettuce para el modo de clúster con TLS habilitado
<a name="BestPractices.Clients-lettuce-cme"></a>

**nota**  
Los tiempos de espera del siguiente ejemplo son para pruebas que ejecutaron comandos SET/GET con claves y valores de hasta 20 bytes de longitud. El tiempo de procesamiento puede ser mayor cuando los comandos son complejos o las claves y los valores son más grandes. Debe establecer los tiempos de espera en función del caso de uso de la aplicación. 

```
// 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();
```

# Ejemplo: Configuración de Lettuce para el modo de clúster deshabilitado con TLS habilitado
<a name="BestPractices.Clients-lettuce-cmd"></a>

**nota**  
Los tiempos de espera del siguiente ejemplo son para pruebas que ejecutaron comandos SET/GET con claves y valores de hasta 20 bytes de longitud. El tiempo de procesamiento puede ser mayor cuando los comandos son complejos o las claves y los valores son más grandes. Debe establecer los tiempos de espera en función del caso de uso de la aplicación. 

```
// 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);
```

## Configuración de un protocolo preferido para clústeres de doble pila (Valkey y Redis OSS)
<a name="network-type-configuring-dual-stack-redis"></a>

En el caso de los clústeres de Valkey o Redis OSS habilitados para el modo de clúster, puede controlar el protocolo que los clientes utilizarán para conectarse a los nodos del clúster con el parámetro de detección de IP. El parámetro de detección de IP se puede establecer en IPv4 o IPv6. 

Para los clústeres de Valkey o Redis OSS, el parámetro de detección de IP establece el protocolo IP utilizado en la salida de las [ranuras del clúster ()](https://valkey.io/commands/cluster-slots/), las [particiones del clúster ()](https://valkey.io/commands/cluster-shards/) y los [nodos del clúster ()](https://valkey.io/commands/cluster-nodes/). Los clientes utilizan estos comandos para detectar la topología del clúster. Los clientes usan las IP de estos comandos para conectarse a los otros nodos del clúster. 

Cambiar la detección de IP no provocará ningún tiempo de inactividad para los clientes conectados. Sin embargo, los cambios tardarán algún tiempo en propagarse. Para determinar cuándo los cambios se han propagado por completo para un clúster de Valkey o Redis OSS, supervise la salida de `cluster slots`. Una vez que todos los nodos devueltos por el comando de ranuras del clúster registren las IP con el nuevo protocolo, los cambios terminarán de propagarse. 

Ejemplo con 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)
```

Ejemplo con 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;
}));
```