Ya hemos hablado de como instalar Varnish para ganar performance en nuestra aplicación web. Utilizar Varnish elimina la necesidad de que una aplicación web regenera la misma página una y otra vez, así ganamos una muchísima velocidad ya que servimos las peticiones web al instante.
Aun así, no es oro todo lo que reluce y el hecho de utilizar Varnish conlleva algunos retos que vamos a discutir: es necesario tener en cuenta qué páginas de nuestro sitio son stateless(sin estado), como son las URL a cachear, tiempo de vida de los objetos en caché, invalidación de la caché… así que antes de adentrarnos en los detalles de la configuración de Varnish vamos a exponer estos temas y a explicar un poco como podemos resolverlos.
Retos
El primero de los retos es cachear la información sin estado. Si tenemos un blog como este, podemos cachear cada una de las páginas en Varnish Caché sin ningún problema, ya que todos los usuarios ven las misma páginas, los mismos artículos,… es decir, no hay personalización por usuario. Por el contrario, si tenemos una tienda online debemos tener en cuenta que en cuanto un usuario añada un producto al carrito, ya debemos personalizar las páginas para indicarle que tiene items en el carrito, por lo que deberemos ser más cautelosos a la hora de cachear toda la página. Una primer paso podría ser modelar todas las acciones de estos usuarios con peticiones POST (que por defecto no se cachean en Varnish).
El objetivo es que mantengamos a los usuarios sin estado tanto tiempo como sea posible. Es decir, intentar no enviar cookies al usuario hasta que no nos sea estrictamente necesario. La idea que debemos llevarnos de este punto es que tenemos que maximizar el numero de HIT en caché, teniendo en cuenta que cachear páginas con información de usuario puede ser peligroso.
Del mismo modo el contenido de la web debe cambiar cada cierto tiempo, imaginemos la portada de un periódico donde las noticias deben ir rotando. Por ello el tiempo de vida de los elementos en la caché debe ser el adecuado. ¿Y cuanto es el adecuado? Depende. Lo mejor es evaluar nuestro negocio y nuestra infraestructura e ir variando el tiempo de vida (TTL) de los objetos en la caché hasta llegar a un punto óptimo. Varnish Agent quizás pueda ayudarnos a tomar estas decisiones.
Por otro lado, no todo el contenido es cacheable. Los sitios web estáticos o con poca personalización (por ejemplo las paginas de about, condiciones de privacidad,..) son fáciles de cachear, pero ¿tiene sentido cachearlas? Al ser casi HTML estático podríamos hacer que no se cachease así tendríamos más sitio en la caché para otras páginas que realmente lo necesiten.
En este punto, cabe destacar que sólo las peticiones GET son fácilmente «cacheables». Por el contrario, las peticiones POST normalmente transmiten información por lo que no deberíamos cachearlas. Esto en vez de tomárnoslo como una limitación, debemos verlo como una ventaja, ya que con las peticiones POST podríamos identificar de alguna manera a los visitantes y personalizar las páginas estáticas.
Un poco de protocolo HTTP
Para entender todo el funcionamiento de Varnish debemos tener unas nociones sobre el protocolo HTTP. EL protocolo HTTP se basa en el envío de mensajes entre un cliente y un servidor. Estos mensajes siguen el esquema de petición-respuesta. Tenemos distintos métodos de petición: HEAD, GET, POST; PUT, DELETE,…y cada respuesta tiene un código de que indica que ha pasado con la petición (200,201,301,302,404,500,…) Además cada petición y respuesta tiene una serie de metadatos llamados cabeceras.
En resumen, tenemos un cliente normalmente un navegador, que hace una petición HTTP ha un servidor. Cliente envía mensaje a servidor. Este mensaje siempre tiene la dirección del servidor (URL) y el método de envío (GET, POST,…), además en esta petición se incluyen una serie de cabeceras para dar más información al servidor (User-agent, Referer,,…)
Cuando la petición del cliente llega al servidor, este la procesa leyendo la URL, el método de envío (GET, POST,…) y las cabeceras (User Agent,…) El servidor lo que hace es «fabricar» una respuesta con información a esa petición. En esa respuesta además se incluye un código para saber que ha pasado con la petición (200, 301, 404,..), también se añaden una serie de cabeceras de respuesta (Content-Type, Content-Length,…) estas cabeceras ayudarán al cliente a entender la respuesta.
Ahora que ya hemos refrescado el protocolo HTTP vamos sacarle partido. Ya que con Varnish podemos leer las peticiones HTTP y actuar dependiendo de las mismas, dar una respuesta u otra. Pero eso será más adelante. Ahora vamos a entender que pasa cada vez que llega una petición a Varnish Caché.
Entendiendo las peticiones en Varnish
Podemos distinguir 3 fases en las peticiones HTTP que llegan a Varnish. Toda petición que llega a Varnish entra en la fase de vcl_recv. En esta fase, leemos información de la petición y comprobamos si el objeto está en caché. Si NO está en caché enviamos la petición al servidor y cuando el servidor termina procesa la petición y «fabrica» la respuesta, dicha respuesta llega a la subrutina vcl_fetch, esta subrutina solo se ejecuta cuando el contenido no estaba en la caché. Por último, la respuesta al navegador la proporciona la subrutina vcl_deliver.
Vamos a explicar con detalle que se hace en cada una de las fases:
- vcl_recv: Aquí podemos leer las cabeceras de la petición HTTP, si tenemos más de un servidos HTTP, aquí podemos elegir que servidor atiende a la petición en caso de que el elemento no esté en caché. Del mismo modo podemos limpiar los parámetros de la petición, por ejemplo eliminando _utm_source ó utm_mediumó utm_campaign,… Finalmente lo más importante de esta fase es que decidimos si un objeto se busca en la caché o se propaga la petición al Backend.
- vcl_fecth: Esta subrutina solo se ejecuta si el contenido no estaba en la caché. En esta fase tenemos disponibles la petición HTTP por si necesitamos leer información de ahí. Incluso añadir datos a la respuesta HTTP (como por ejemplo saber si la petición ha sido HIT o MISS, o eliminar cabeceras como X-Powered-By)
- vcl_deliver: Todas las respuestas pasan por esta fase, es justo antes de enviar dicha respuesta al navegador. En esta fase se indica el tiempo que el objeto pasará en cache (TTL). Podemos utilizar esta fase para incluir además información adicional en las cabeceras.
Aparte de estas 3 subrutinas (vcl_recv, vcl_deliver,vcl_fect), tenemos algunas más, como veremos en el siguiente esquema.
Explicarlas todas, será quizá en otro post.
Ya tenemos claro que rutinas son por las que pasa una petición HTTP, ahora ¿donde se configuran esas rutinas? Como ya comentamos en el anterior post (Entendiendo Varnish Caché) toda la configuración de Varnish se hace en el archivo /etc/varnish/default.vcl
Es ahí donde están las subutinas que mencionamos antes y donde podemos escribir código para personalizarlas.
Un archivo de configuración de ejemplo
Aunque en github y por Internet podemos encontrarnos con un montón de templates como este https://github.com/mattiasgeniar/varnish-4.0-configuration-templates/blob/master/default.vcl o https://gist.github.com/reifman/4651531 aquí vamos a escribir uno que nos sirva para acercarnos a la sintaxis de Varnish.
import std;
backend default {
.host = "localhost"; # Varnish is running on same server as Apache
.port = "80";
}
sub vcl_recv {
# Remove Google Analytics cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
# Strip hash, server doesn't need it.
if (req.url ~ "\#") {
set req.url = regsub(req.url, "\#.*$", "");
}
}
sub vcl_fetch {
if (req.http.cookie ~ "JSESSIONID" || req.request == "POST") {
std.log("not removing cookie/passing POST, url " + req.url);
return (pass);
} else {
# remove all other cookies and prevent backend from setting any
std.log("removing cookie in url " + req.url);
unset beresp.http.set-cookie;
set beresp.ttl = 600s;
}
}
sub vcl_deliver {
# send some handy statistics back, useful for checking cache
if (obj.hits > 0) {
set resp.http.X-Cache-Action = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache-Action = "MISS";
}
}
Vemos que la sintaxis es bastante parecida a C. De hecho el código se compila y se utiliza como un objeto compartido en memoria para optimizar el rendimiento.
Las subrutinas se identifican con sub y el nombre de la subrutina. El código de la subrutina se encierra entre corchetes.
La idea que hay que tener aquí, a Varnish Caché le llegan peticiones HTTP que contienen una URL y tenemos que trabajar con dicha petición. Analizaremos la URL para saber si la esa petición está en caché, o si no queremos cachearla y dejarla pasar al backend.
Quizás en un principio la sintaxis parezca compleja, e incluso las variables sean dificiles de entender y mucho más cuando el paradigma de Varnish es imperativo. Como ayuda, estas son las variables disponibles en cada subrutina.
- req: es la petición HTTP de alto nivel contiene la URL, las cookies…
- resp: es la respuesta antes de que se envíe al cliente, cuando todavía se puede manipular, añadir cabeceras,…
- beresp: Es la respuesta que Varnish obtiene del Backend.
No lo hemos dicho hasta ahora, pero eliminar objetos de la caché es sencillo haciendo peticiones PURGE, pero no debemos preocuparnos, Varnish da para muchos más posts.
Conclusiones
Este post es una toma de contacto más directa con Varnish, hemos visto por encima las fases de una petición HTTP, hemos ojeado la sintaxis del lenguaje VCL de Varnish y hemos comentado algunas de las cosas qué podemos hacer con Varnish y algunos retos a los que debemos enfrentarnos cuando nos decantamos por cachear parte o todo nuestro sitio web con Varnish. Todavía nos queda mucho camino que recorrer con Varnish, pero poco a poco seguro que vamos comprendiendo todo lo referente a esta potente herramienta, incluso como hacer test sin morir en el intento 😉
Recursos
Lo repetiremos una y 100 veces, el mejor recurso para Varnish es el libro de Varnish http://book.varnish-software.com/4.0/chapters/VCL_Basics.html del mismo modo también la ayuda man está muy conseguida.
Un comentario en “Sacando partido a Varnish Cache”