HTTP/1.1 y el bloqueo de cabecera de línea
Qué es exactamente el head-of-line blocking, en qué capa ocurre en HTTP/1.1, por qué el pipelining fracasó, y qué prácticas de la época siguen circulando sin sentido.
El bloqueo de cabecera de línea es el concepto que organiza toda la evolución de HTTP, y se explica mal con frecuencia porque ocurre en capas distintas según la versión. En HTTP/1.1 ocurre en la capa de aplicación y tiene un remedio parcial que el navegador aplica sin que nadie se lo pida: abrir varias conexiones. Entender ese remedio y sus límites es lo que explica media docena de prácticas heredadas.
- Definir el bloqueo de cabecera de línea y localizar en qué capa ocurre en HTTP/1.1.
- Explicar por qué el pipelining no resolvió el problema y quedó deshabilitado.
- Justificar el límite de conexiones simultáneas por origen y sus consecuencias.
- Reconocer qué optimizaciones de la época han quedado obsoletas y cuáles no.
Qué es el bloqueo de cabecera de línea
Es el efecto por el que un elemento al frente de una cola impide avanzar a todos los que están detrás, aunque esos podrían atenderse. El nombre viene de las redes conmutadas y describe una situación completamente general: una cola con atención estrictamente ordenada donde el primero se atasca.
En HTTP/1.1 ocurre en la capa de aplicación y por una razón de diseño explícita: una conexión solo puede atender una petición a la vez. El cliente envía una petición, espera la respuesta completa, y solo entonces puede enviar la siguiente.
Si la primera petición es un endpoint que tarda dos segundos en responder, y detrás quieres pedir una hoja de estilos de 4 KB que el servidor tiene lista al instante, la hoja de estilos espera dos segundos. No hay nada roto: el protocolo funciona exactamente como se diseñó.
El coste no es solo el tiempo perdido. Es que la conexión está ociosa mientras espera: no se transfieren bytes, la ventana de congestión no crece, y el ancho de banda no se usa.
El intento fallido: pipelining
HTTP/1.1 introdujo el pipelining precisamente para esto: permitir enviar varias peticiones seguidas sin esperar cada respuesta.
Resolvía la mitad del problema, el retardo de envío, y no resolvía la otra mitad, que es la que importa: las respuestas siguen teniendo que llegar en el mismo orden en que se pidieron. Si la primera respuesta tarda, las demás siguen bloqueadas aunque ya estén generadas. El bloqueo se mueve del envío a la recepción, y sigue estando ahí.
Además, el pipelining resultó tener problemas prácticos serios: los intermediarios de la época, proxies y cortafuegos, lo implementaban mal o directamente rompían las conexiones, y un fallo a mitad de una tubería obligaba a reenviar peticiones con semántica dudosa. El resultado fue que los navegadores lo deshabilitaron por defecto y en la práctica nunca se usó en la web abierta.
El remedio real: varias conexiones
Como el protocolo no daba paralelismo, los navegadores lo consiguieron por fuera: abriendo varias conexiones TCP simultáneas al mismo origen. El límite convencional se estabilizó en seis conexiones por origen, un número que no está en ninguna especificación de HTTP sino que es una decisión de implementación compartida por todos los navegadores.
Seis conexiones dan seis peticiones en vuelo, lo cual mitiga el bloqueo. Pero el remedio tiene un coste alto y poco visible:
- Seis establecimientos. Seis saludos TCP y seis negociaciones TLS al mismo servidor. En una primera visita, eso es mucho tiempo y mucho trabajo criptográfico.
- Seis arranques lentos independientes. Cada conexión parte de su propia ventana de congestión mínima. Ninguna alcanza velocidad de crucero y todas compiten entre sí por el mismo enlace.
- Seis veces el estado en el servidor. Memoria, descriptores de fichero, contexto de TLS.
- Peor control de congestión. Seis flujos independientes compitiendo se comportan peor que uno solo bien gestionado, y se reparten mal el enlace con el resto del tráfico del usuario.
Y sobre todo: seis no son suficientes. Una página moderna carga decenas o cientos de recursos. Con seis carriles, el resto hace cola.
El límite es un compromiso entre paralelismo y comportamiento en red. Subirlo mejora el paralelismo del navegador a costa de empeorar el control de congestión global y de cargar más los servidores, que además no pueden distinguir a un cliente legítimo de uno abusivo. Seis fue el punto que la industria consideró aceptable, y se ha mantenido porque HTTP/2 hizo innecesario tocarlo.
El sharding de dominios y su muerte
De la limitación de seis conexiones por origen salió la técnica más característica de la época: repartir los recursos entre varios subdominios para multiplicar el límite. Con estatico1.ejemplo.com, estatico2.ejemplo.com y estatico3.ejemplo.com, el navegador abría hasta dieciocho conexiones.
Funcionaba, y era una carrera de fondo contra la propia física: cada subdominio nuevo añadía su propia resolución de DNS y sus propios establecimientos, así que el beneficio se saturaba rápido y a partir de tres o cuatro subdominios el remedio era peor que la enfermedad.
Con HTTP/2 esta técnica se volvió activamente perjudicial: fragmenta el tráfico en varias conexiones cuando el protocolo está diseñado para multiplexar sobre una sola, impide que la ventana de congestión crezca de forma conjunta, y rompe la priorización, que solo funciona dentro de una misma conexión.
Si encuentras sharding de dominios en un sitio moderno, es deuda técnica que hay que eliminar.
Qué ha quedado obsoleto y qué no
Un balance de las prácticas de la época de HTTP/1.1, porque varias siguen circulando como buenas prácticas.
| Práctica | Estado hoy | Por qué |
|---|---|---|
| Sharding de dominios | Obsoleta y perjudicial | Fragmenta la multiplexación |
| Concatenar todos los CSS en un archivo | Matizada | Con multiplexación varias peticiones son baratas; concatenar impide cachear por separado |
| Concatenar todos los scripts en un bundle | Matizada | Igual, y además impide cargar solo lo necesario |
| Sprites de imágenes | Obsoleta en general | Cada petición ya no cuesta una conexión; el sprite invalida todo al cambiar una imagen |
| Incrustar imágenes como URL de datos | Casi siempre mala | No se cachean por separado, engordan el documento, y añaden coste de decodificación |
| Reducir el número de peticiones porque sí | Obsoleta | Lo caro son los orígenes, no las peticiones |
| Mantener las conexiones vivas | Sigue siendo esencial | Evita repagar el establecimiento |
| Comprimir texto | Sigue siendo esencial | Independiente de la versión de HTTP |
| Reducir el número de orígenes | Más importante que nunca | Cada origen cuesta una conexión y un arranque lento |
La lección que atraviesa la tabla: las técnicas que atacaban el número de peticiones han caducado; las que atacan el número de bytes o el número de conexiones siguen vigentes. Es una buena regla para evaluar cualquier consejo de rendimiento que encuentres sin fecha.
Dónde sigue vivo HTTP/1.1
Aunque la web pública se ha movido mayoritariamente a HTTP/2 y HTTP/3, sigue habiendo tráfico por HTTP/1.1 y conviene saber dónde.
- Comunicación interna entre servicios. Muchos backends hablan HTTP/1.1 entre sí, donde el bloqueo importa menos porque las latencias son de microsegundos.
- Servidores antiguos y equipos empotrados. Cualquier cosa que no se haya actualizado.
- Conexiones degradadas por intermediarios. Un proxy corporativo o un antivirus que intercepta TLS puede forzar la caída a HTTP/1.1 sin que el usuario lo sepa. Es un caso real y frecuente en entornos empresariales, y solo se detecta mirando
nextHopProtocolen datos de campo. - Peticiones que no pasan por el navegador. Herramientas de línea de comandos, scripts, integraciones.
La forma en que suele contarse esta historia es que HTTP/2 resolvió el bloqueo de cabecera de línea. Es cierto solo en la capa de aplicación. HTTP/2 multiplexa varios flujos lógicos sobre una única conexión TCP, y TCP entrega los bytes estrictamente en orden: si se pierde un segmento, el sistema operativo retiene todo lo que llega después hasta que se retransmita el perdido, sin saber ni poder saber que esos bytes pertenecen a flujos lógicos distintos que podrían entregarse ya. El bloqueo se mudó de la capa de aplicación a la de transporte, y en redes con pérdida se volvió peor que con HTTP/1.1, porque con seis conexiones una pérdida solo bloqueaba un sexto del tráfico y con una sola conexión lo bloquea todo. Esa es la razón exacta por la que existe QUIC, y la razón por la que HTTP/2 puede rendir por debajo de HTTP/1.1 en una red móvil mala. Cuando alguien te diga que HTTP/2 elimina el bloqueo, la pregunta correcta es en qué capa.