El tiempo hasta el primer byte (TTFB) de un sitio web es el tiempo desde que el usuario comienza a navegar hasta que el código HTML de la página que ha solicitado empieza a llegar. Un TTFB lento ha sido la pesadilla de mi existencia los más de diez años que llevo ejecutando WebPageTest.
Hay una razón por la que el TTFB aparece como una de las pocas "notas" que WebPageTest le adjudica a los sitios y, específicamente, por la que es la primera nota de la lista.
Si el primer byte es lento, TODAS las otras métricas también serán lentas. Su mejora es uno de los pocos elementos que te permite predecir cuál será el impacto sobre las otras medidas. Cada mejora de un milisegundo en el TTFB se traduce directamente en un milisegundo de ahorro en cada una de las otras medidas (es decir, los primeros contenidos serán 500 ms más rápidos si el TTFB mejora 500 ms). Dicho esto, un TTFB rápido no garantiza una experiencia rápida, pero un TTFB lento garantiza una experiencia lenta. Calculo que aproximadamente 50 % de todas las solicitudes de ayuda sobre los resultados de WebPageTest proceden de los propietarios de sitios que tienen problemas con un TTFB lento.
Pueden juntarse muchas cosas con el TTFB, incluidos los redireccionamientos, el DNS, la configuración de conexión, la negociación de SSL y el tiempo real de respuesta del servidor. La mayoría de ellas se arreglan con cierta facilidad usando un servicio como Cloudflare, pero el tiempo de respuesta del servidor para HTML en sí es a menudo el problema mayor y más difícil de resolver.
El gráfico de cascada a continuación muestra el tiempo de respuesta del servidor como una barra azul claro en la primera solicitud y puede ser vergonzosamente obvio cuando es lento. En condiciones óptimas, el tiempo de respuesta del servidor no sería más largo que la barra naranja del socket de conexión que va justo antes.
La lentitud de respuesta de origen puede estar causada por problemas muy diversos, desde la configuración del servidor, la carga del sistema, las bases de datos del back-end y los sistemas con los que se comunica, hasta el propio código de la aplicación. Llegar a la raíz de los problemas de rendimiento generalmente implica equipos de ingenieros desarrolladores de operaciones que trabajen con herramientas de Gestión de rendimiento de aplicaciones para rastrear las partes más lentas de la aplicación y mejorarlas.
Buena parte de los propietarios de sitios con quienes he trabajado no tienen recursos o conocimientos para realizar ese tipo de investigación. En la mayoría de los casos, habían contratado de manera puntual a un desarrollador para que creara su sitio o lo habían hecho ellos mismos en WordPress y lo alojaban con el hosting más económico que habían encontrado. El hosting generalmente está diseñado para ejecutar tantos sitios como sea posible, no necesariamente con el máximo rendimiento.
Almacenamiento perimetral de HTML
En realidad, la mayoría del HTML no es especialmente dinámica. Tiene que poder cambiar relativamente rápido cuando se actualiza el sitio, pero en gran parte de la web el contenido es estático durante meses o años.
Existen casos especiales, como cuando un usuario inicia sesión (como administrador u otro) donde el contenido difiere, pero la gran mayoría de visitas son de usuarios anónimos. Si el HTML puede almacenarse en caché y servirse directamente desde el perímetro, entonces la mejora del rendimiento puede ser considerable (unos 3 segundos más rápido en todas las métricas en este caso).
Hay docenas de plugins para WordPress para el almacenamiento en caché de contenido en el origen, pero requieren configuración (dónde almacenar las páginas) y el rendimiento depende todavía en gran medida del rendimiento del hosting. Si se aparta contenido para su almacenamiento perimetral se reduce la complejidad, se elimina el tiempo adicional para volver al origen y se suprime por completo el rendimiento del hosting de la ecuación. También puede reducirse significativamente la carga sobre los sistemas de alojamiento al descargar todo el tráfico anónimo.
Cloudflare es compatible con el almacenamiento caché estático de HTML, y los clientes comerciales y empresariales pueden posibilitar que los usuarios con sesión iniciada se salten la caché habilitando "evitar almacenamiento de cookies". Esto funciona en tándem con el plugin de Cloudflare para WordPress, así que la caché se puede borrar cuando se actualiza el contenido. Hay también otros plugins de almacenamiento que se integran con varias redes de distribución de contenidos, pero en todos los casos tienen que configurarse con claves de API para la red de distribución de contenidos y las implementaciones son específicas para cada una de ellas.
Almacenamiento perimetral de HTML con cero configuración
Para que se adopte ampliamente, tenemos que hacer que el almacenamiento de HTML en caché se produzca automáticamente (o lo más parecido a automáticamente como sea posible). Con ese fin, necesitamos una vía de comunicación entre un origen (como un sitio de WordPress) y un almacenamiento perimetral (como nodos perimetrales de Cloudflare) para gestionar una caché remota que pueda purgarse explícitamente.
El origen debe poder:
- Detectar cuándo tiene delante un almacenamiento perimetral compatible.
- Especificar el contenido que debe almacenarse en caché y para qué visitantes (es decir, visitas sin cookie de inicio de sesión).
- Purgar el contenido almacenado en caché cuando ha cambiado (globalmente en todos los perímetros).
En lugar de requerir que el origen se integre con una API para que se purguen los cambios y requerir configuración manual para ver qué almacenar en caché y cuándo, podemos hacer todo eso con encabezados HTTP de las peticiones que van y vienen entre los perímetros y el origen:
1 - Un encabezado HTTP se añade a las peticiones que van desde el perímetro hasta el origen para anunciar que existe un almacenamiento perimetral y las capacidades con las que es compatible:
x-HTML-Edge-Cache: supports=cache|purgeall|bypass-cookies
2 - Cuando el origen responde con una página almacenable en caché, añade un encabezado HTTP en la respuesta para indicar que debe almacenarse en caché y las normas para cuando la versión almacenada no se debe utilizar (para posibilitar que se evite el almacenamiento en caché de usuarios con sesión iniciada):
x-HTML-Edge-Cache: cache,bypass-cookies=wp-|wordpress
En este caso, se almacenará en caché el código HTML, pero las solicitudes que tienen cookies que comienzan con "wordpress" o "wp-" en su nombre evitarán el almacenamiento e irán al origen.
3 - Cuando una solicitud modifica el contenido del sitio (actualiza un post, cambia un tema, agrega un comentario) el origen agrega un encabezado de respuesta HTTP que indica que la caché debe purgarse:
x-HTML-Edge-Cache: purgeall
La única parte complicada de gestionar es que la purga tiene que borrar la caché de TODOS los perímetros, no solo el que traspasó la solicitud.
4 - Cuando aparece una nueva solicitud para HTML que está en el almacenamiento perimetral, las cookies de petición se cotejan con las normas para la respuesta almacenada en caché. Si las cookies no están presentes, se sirve la versión almacenada en caché; de lo contrario, la solicitud se traspasa al origen.
Con este sencillo comando basado en el encabezado y esta interfaz de control, podemos eliminar la necesidad de que un origen se comunique con una API y de cualquier configuración explícita. También hace que la lógica del origen sea significativamente más fácil de implementar, ya que no hay ninguna configuración (o interfaz de usuario) y no hay necesidad de realizar solicitudes salientes a una API específica de un proveedor. El ejemplo del plugin de WordPresses menos de 50 líneas de código que en su gran mayoría conecta retrollamadas de todos los eventos que cambian contenidos.
Empezar a almacenar hoy con WordPress y Workers
Una de las cosas que más me gustan de Workers es que te da un perímetro totalmente programable para experimentar con ideas e implementar tu propia lógica. He creado un script de Worker que implementa el protocolo basado en el encabezado y el almacenamiento perimetral de Cloudflare y un plugin para WordPress que implementa la lógica del origen de WordPress.
La única parte delicada de Worker era encontrar una manera de purgar elementos de la caché globalmente. Las cachés de Worker son locales en cada perímetro y no proporcionan una interfaz para hacer operaciones globales. Una de las formas de hacerlo es utilizar la API de Cloudflare para purgar la caché global, pero es un poco fuerte (purga todo de la caché, incluidas secuencias de comandos e imágenes) y requiere cierta configuración. Si sabes qué URL específicas se cambiarán al cambiar un contenido, hacer una purga dirigida en la API solo de esas URL sería probablemente la mejor solución.
Utilizando el nuevo Almacén KV de Workers podemos purgar la caché de una manera diferente. El script de Worker utiliza un esquema de versionado de la caché donde cada URL obtiene un número de versión que se añade a él (es decir, http://www.example.com/?cf_edge_cache_ver=32). La URL modificada solo se usa localmente por parte del Worker como una clave para las respuestas almacenadas en caché y el número actual de la versión se guarda en KV, que es un almacén global. Cuando se purga la caché, el número de versión se incrementa, lo que cambia la URL de todos los recursos. Las entradas más antiguas irán quedando fuera de la caché con normalidad, ya que no se accederá a ellas. Requiere un pequeño ajuste configurar KV para Worker, pero esperemos que en algún momento futuro pueda ser automático.
¿Y luego?
Creo que tiene un gran valor para la web la estandarización de una forma de que el almacenamiento perimetral y el origen se comuniquen para almacenar en caché contenido dinámico. Incentivaría que los sistemas de gestión de contenidos creen apoyo directamente en las plataformas y proporcionen una interfaz estándar que podría usarse con diferentes proveedores (e incluso para almacenamiento perimetral local en equilibradores de carga u otros servidores de proxy inverso). Después de hacer algunas pruebas más con diferentes tipos de sitios, estoy pensando en traer el concepto al Grupo de trabajo de HTTP del IETF para ver si podemos crear una norma oficial para las cabeceras de control (usando nombres diferentes). Si tienes comentarios sobre cómo debería funcionar o qué características habría que exponer, me encantaría que me lo contaras (como purgar URL específicas, variar contenidos para móvil/ordenador o por región, expandirlo para cubrir todos los tipos de contenido, etc.).