Una página que responde en 200 ms contra un Redis local tarda cuatro segundos en cuanto el cliente se sitúa detrás de una VPN, mientras los contadores del servidor siguen inactivos. El registro lento no muestra nada por encima de un milisegundo, el gráfico de CPU es plano y la instancia atiende a otros clientes sin quejarse. Lo que ha cambiado es la distancia, y lo que multiplica la distancia es el número de veces que su código se detiene y espera. Recorra la ruta de la petición desde el cliente hacia fuera y la solución casi siempre son menos esperas secuenciales en lugar de un servidor más rápido.

Empiece por el cliente, no por las métricas del servidor

Observe el proceso del cliente entre el primer comando y el último. Si la traza muestra una respuesta, una pausa y luego el siguiente comando, está pagando un viaje completo por comando y ningún ajuste del servidor lo cambiará. El registro lento es el instrumento equivocado aquí, porque registra el tiempo de ejecución y excluye deliberadamente la E/S de comunicación. Un registro lento limpio junto a un endpoint lento es la firma de un problema de viajes de ida y vuelta.

Mida la ruta misma antes de tocar el código. redis-cli -h redis.example.internal --latency envía PING repetidos e imprime el tiempo medio de petición y respuesta, que es su coste por intercambio. Multiplíquelo por el número de comandos secuenciales en una acción de usuario y tendrá un mínimo para el endpoint.

Cuente los viajes que una acción de usuario necesita de verdad

Tome un solo endpoint y anote cada llamada a Redis en orden, incluidas las que se esconden dentro de un ORM, un middleware de sesión o una búsqueda de feature flag. La mayoría de los endpoints lentos acaban haciendo veinte o treinta llamadas donde tres bastarían, y las ocultas suelen ser la mayoría.

Una cadena de dependencias no se puede colapsar: si el segundo comando necesita la clave que devuelve el primero, la espera es real y las únicas opciones son otro modelo de datos o una operación en el servidor que exprese todo el paso a la vez. Las lecturas independientes son otro caso, porque nada le obliga a esperar la primera respuesta antes de enviar la segunda. Marque cada llamada como dependiente o independiente antes de editar nada, y la forma de la solución se vuelve obvia.

El número que importa son las esperas secuenciales, no los comandos totales. Treinta comandos en un lote siguen costando una espera.

Agrupe solo los comandos que no dependen unos de otros

Una canalización pone en cola comandos independientes y recoge las respuestas en orden, convirtiendo N esperas en una. La mayoría de los clientes la exponen como bloque, callback o gestor de contexto en lugar de un modo de conexión aparte, así que compruebe la API que trae su versión instalada. La ganancia escala con el tiempo de viaje, y por eso mismo importa en una VPN y apenas se nota en localhost.

import redis

r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
    pipe.get(key)
print(pipe.execute())

Pase transaction=False cuando solo quiera agrupar. Con el valor predeterminado el cliente envuelve el lote en MULTI y EXEC, lo que lo hace atómico y cambia lo que otros clientes pueden observar en vuelo. La atomicidad es una decisión de corrección, no un botón de rendimiento, y una canalización no es una transacción solo porque ambas reduzcan viajes.

Lea cada respuesta en lugar de solo el envío del lote. Un comando en cola puede devolver un error mientras el lote en conjunto se completa, y un cliente que descarta la lista de respuestas convierte una escritura fallida en una silenciosa. Compare la longitud de la lista de respuestas con el número de comandos que puso en cola.

Mantenga viva la conexión entre acciones

Cada conexión nueva paga resolución de nombres, un handshake TCP, un handshake TLS y un AUTH, y por VPN cada uno de esos es un viaje que no puede eliminar agrupando. Cree el cliente una vez por proceso y reutilícelo, o use el pool que ofrece su biblioteca con un tamaño acotado. Reconectar por petición es la segunda causa más común de este síntoma después de las esperas por comando.

Mantenga el nombre de host privado en la configuración en lugar de una dirección literal. La validación del certificado y la identidad del servicio siguen aplicándose en una red privada, y fijar una IP se rompe en el momento en que el proveedor renumera una subred. Si la resolución es la parte lenta, arregle la ruta del resolutor en lugar de la cadena de conexión.

Defina un tiempo de espera de socket explícito para que un túnel atascado falle rápido en lugar de colgar a un trabajador. Luego confirme que el pool se reutiliza de verdad registrando la creación de conexiones en el cliente.

Dimensione el lote antes de que se convierta en un problema de memoria

Redis retiene las respuestas en cola durante todo el lote, y el cliente retiene después el resultado analizado. Una canalización de diez mil valores grandes cambia esperas de red por picos de memoria y pausas largas en ambos extremos.

Elija el tamaño del lote según el volumen de respuesta y no el número de comandos. Unos cientos de comandos pequeños en un lote suelen ser cómodos, mientras que cien cargas de varios kilobytes no lo son. Divida por bytes y también por número, y mantenga cada lote dentro del presupuesto de latencia que el endpoint tiene de verdad.

Mida de extremo a extremo después del cambio: redis-cli -h redis.example.internal --latency-history muestra si la ruta misma es estable, y su propio temporizador de peticiones muestra si el endpoint mejoró. Si el endpoint sigue lento mientras el historial de latencia es plano, el coste restante es trabajo del servidor o su propia serialización, y el siguiente paso es un perfil del gestor en lugar de otro cambio de agrupación.

Artículos relacionados