Keep-Alive, en el contexto de conexiones persistentes a través de un proxy, es un mecanismo HTTP que permite que una única conexión TCP permanezca abierta y sea reutilizada para múltiples solicitudes y respuestas HTTP entre un cliente y un proxy, y entre un proxy y un servidor de origen, reduciendo así la sobrecarga de establecer nuevas conexiones para cada transacción.
Entendiendo Keep-Alive
HTTP Keep-Alive, también conocido como conexiones persistentes, es una optimización fundamental para el rendimiento web. Sin Keep-Alive, cada solicitud HTTP requeriría el establecimiento de una nueva conexión TCP (handshake TCP), seguido de la transferencia de datos y luego la terminación de la conexión. Este proceso conlleva una sobrecarga significativa, especialmente para las conexiones seguras que también requieren un handshake TLS.
Beneficios de las Conexiones Persistentes
- Latencia Reducida: Elimina la latencia asociada con el handshake TCP y TLS para solicitudes posteriores sobre la misma conexión. Esto es particularmente notorio para clientes con tiempos de ida y vuelta (RTT) altos.
- Menor Uso de CPU y Memoria: El establecimiento y la terminación menos frecuentes de conexiones reduce la carga computacional en clientes, proxies y servidores de origen. Esto incluye ciclos de CPU para operaciones de socket, memoria para el estado de la conexión y uso de descriptores de archivo.
- Menor Congestión de Red: Menos conexiones TCP abriéndose y cerrándose significa menos tráfico de señalización (paquetes SYN, SYN-ACK, FIN, FIN-ACK) en la red.
- Mejor Rendimiento (Throughput): Permite un uso más eficiente de los mecanismos de control de congestión de TCP, ya que las conexiones pueden alcanzar tasas de rendimiento más altas con el tiempo.
Keep-Alive en HTTP/1.x
La implementación y el comportamiento predeterminado de Keep-Alive difieren entre HTTP/1.0 y HTTP/1.1.
Keep-Alive en HTTP/1.0
En HTTP/1.0, las conexiones persistentes no eran el comportamiento predeterminado. Los clientes tenían que solicitar explícitamente Keep-Alive incluyendo el encabezado Connection: keep-alive en sus solicitudes. Los servidores responderían con el mismo encabezado para indicar su soporte para conexiones persistentes. Si ninguna de las partes incluía este encabezado, la conexión se cerraría después de la respuesta actual.
Keep-Alive en HTTP/1.1
HTTP/1.1 hizo de las conexiones persistentes el comportamiento predeterminado. A menos que se indique explícitamente lo contrario, los clientes y servidores asumen que una conexión debe permanecer abierta después de una transacción. Para cerrar una conexión, cualquiera de las partes debe enviar el encabezado Connection: close. Este cambio mejoró significativamente el rendimiento web por defecto.
El Encabezado Connection como Encabezado Hop-by-Hop
El encabezado Connection es un encabezado hop-by-hop (salto a salto). Esto significa que está destinado únicamente a la conexión directa entre dos partes que se comunican (por ejemplo, cliente a proxy, o proxy a servidor de origen) y no debe ser retransmitido por proxies o intermediarios. Los proxies deben eliminar o modificar los encabezados hop-by-hop antes de reenviar solicitudes o respuestas. No hacerlo puede llevar a violaciones de protocolo y comportamientos inesperados, ya que el servidor o cliente posterior podría malinterpretar las instrucciones de gestión de conexión.
Otros encabezados hop-by-hop comunes incluyen Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers y Upgrade.
Keep-Alive a Través de un Proxy
Los proxies desempeñan un papel crítico en la gestión de conexiones persistentes. Típicamente mantienen dos conjuntos de conexiones persistentes:
- Conexiones Cliente-a-Proxy: El proxy mantiene conexiones persistentes con sus clientes.
- Conexiones Proxy-a-Origen: El proxy mantiene conexiones persistentes con los servidores de origen a los que reenvía las solicitudes.
Rol del Proxy en la Gestión de Encabezados
Cuando un proxy recibe un encabezado Connection: keep-alive de un cliente, entiende que el cliente quiere mantener la conexión abierta con el proxy. Sin embargo, el proxy no debe reenviar este encabezado Connection al servidor de origen. En su lugar, el proxy decide de forma independiente si establecer una conexión persistente con el servidor de origen basándose en su propia configuración y las capacidades del servidor de origen.
Un patrón común para los proxies, especialmente al tratar con HTTP/1.1, es eliminar el encabezado Connection de las solicitudes entrantes del cliente y luego gestionar la conexión ascendente al servidor de origen por separado. Si el proxy quiere usar conexiones persistentes HTTP/1.1 en sentido ascendente, simplemente omite el encabezado Connection: close.
# Ejemplo de configuración de Nginx para proxying HTTP/1.1 con Keep-Alive
location / {
proxy_pass http://backend_server;
proxy_http_version 1.1; # Instruye a Nginx para usar HTTP/1.1 en sentido ascendente
proxy_set_header Connection ""; # Elimina el encabezado Connection de las solicitudes del cliente
# para asegurar que Nginx gestione Keep-Alive en sentido ascendente de forma independiente.
# Esto es crucial para el manejo adecuado de los encabezados hop-by-hop.
}
En este ejemplo de Nginx, proxy_http_version 1.1; asegura que Nginx utilice HTTP/1.1 al conectarse a backend_server. Por defecto, las conexiones HTTP/1.1 son persistentes. proxy_set_header Connection ""; elimina explícitamente el encabezado Connection que podría haber venido del cliente, evitando que sea reenviado al servidor de origen y permitiendo a Nginx gestionar correctamente sus propias conexiones persistentes ascendentes.
Pool de Conexiones
Los proxies a menudo implementan un pool de conexiones para sus conexiones ascendentes a los servidores de origen. Esto significa que mantienen un conjunto de conexiones TCP inactivas y abiertas a varios servidores de origen. Cuando llega una nueva solicitud para un origen específico, el proxy primero verifica si hay una conexión inactiva a ese origen disponible en el pool. Si es así, reutiliza esa conexión. Si no, se establece una nueva conexión. Esto mejora aún más la eficiencia al reducir el número de nuevas conexiones que el proxy necesita establecer.
Tiempos de Espera de Keep-Alive y Gestión de Recursos
Aunque beneficiosas, las conexiones persistentes requieren una gestión cuidadosa para evitar el agotamiento de recursos en el proxy y los servidores de origen.
Mecanismos de Tiempo de Espera
Cada parte (cliente, proxy, servidor de origen) puede especificar un tiempo de espera keep-alive. Si no se intercambian datos en una conexión persistente dentro de este período de tiempo de espera, la conexión se cierra.
- Tiempo de Espera del Lado del Cliente: El cliente podría cerrar su conexión con el proxy si no envía otra solicitud dentro de un cierto período.
- Tiempo de Espera del Lado del Proxy: El proxy puede configurar sus propios tiempos de espera
keep-alivetanto para las conexiones orientadas al cliente como para las orientadas al origen. Si una conexión cliente-a-proxy o proxy-a-origen inactiva excede su tiempo de espera, el proxy la cerrará. - Tiempo de Espera del Servidor de Origen: El servidor de origen cerrará su conexión con el proxy si permanece inactiva durante demasiado tiempo.
Los tiempos de espera no coincidentes pueden provocar problemas. Por ejemplo, si un servidor de origen tiene un tiempo de espera keep-alive más corto que el proxy, el origen podría cerrar una conexión que el proxy todavía cree que está abierta. Cuando el proxy intente reutilizar esta conexión "obsoleta", encontrará un error (por ejemplo, un restablecimiento de conexión) y tendrá que reintentar la solicitud en una nueva conexión. Esto añade latencia y aumenta las tasas de error. Por lo tanto, generalmente es aconsejable configurar los tiempos de espera keep-alive de proxy-a-origen para que sean ligeramente más cortos o iguales a los tiempos de espera del servidor de origen.
Conexiones Máximas
Los proxies también necesitan limitar el número total de conexiones persistentes que mantienen, tanto con los clientes como con los servidores de origen. Cada conexión abierta consume recursos del sistema (memoria, descriptores de archivo). Las conexiones persistentes ilimitadas pueden llevar a:
- Agotamiento de Descriptores de Archivo: El proxy se queda sin descriptores de archivo disponibles.
- Agotamiento de Memoria: Demasiadas conexiones abiertas consumen memoria excesiva.
- Aumento de la Carga de CPU: La gestión de un gran número de conexiones inactivas aún conlleva cierta sobrecarga.
Las configuraciones de proxy típicamente permiten a los administradores establecer keep-alive_timeout y keep-alive_requests (el número máximo de solicitudes permitidas sobre una única conexión persistente antes de que se cierre y se vuelva a crear).
# Ejemplo de Nginx: Configuración de Keep-Alive para conexiones orientadas al cliente
http {
keepalive_timeout 60s; # Tiempo de espera keep-alive de cliente a Nginx
keepalive_requests 1000; # Máx. solicitudes por conexión keep-alive de cliente a Nginx
# ...
}
# Ejemplo de Nginx: Configuración de Keep-Alive para conexiones de proxy a origen
location / {
proxy_pass http://backend_server;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Opcional: Configurar ajustes específicos de keep-alive para el upstream si es necesario
# El comportamiento predeterminado de keep-alive de Nginx para el upstream es reutilizar conexiones
# siempre que no sean cerradas explícitamente por el upstream o por tiempo de espera de Nginx.
# Para un control más explícito sobre el pool de conexiones del upstream:
# proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
# proxy_connect_timeout 5s;
# proxy_read_timeout 10s;
# proxy_send_timeout 10s;
}
Keep-Alive en HTTP/2 y Más Allá
HTTP/2 introdujo la multiplexación, permitiendo que múltiples solicitudes y respuestas HTTP se envíen concurrent