Una caché, por definición, es un almacenamiento a corto plazo de información y con una altísima velocidad de acceso, permitiéndonos mejorar el rendimiento de nuestros sistemas. En el caso de la web podemos diferenciar dos tipos:
- Caché de cliente, que se hace en el navegador y que nos permite no tener que traer de internet imágenes o ficheros de estilo una vez hemos obtenido una copia.
- Caché de servidor, que guarda una copia de manera temporal para evitar sobrecarga de nuestro servidor de aplicaciones.
Esto último tiene una justificación, y es que el acceso a las aplicaciones (PHP, ASP.net, Ruby) es un proceso relativamente lento que suele estar acompañado de un acceso a una base de datos, una aplicación de la lógica de negocio y la composición final de la página.
Existen diferentes sistemas y tipos de caché, como Memcached o Redis. En este artículo hablaremos de Varnish, un proyecto open-source que se instala sobre sistemas UNIX (aunque tiene soporte para Windows mediante Cygwin).
La principal peculiaridad que tiene este sistema, además de sus casi-infinitas opciones de configuración, es que permite dividir nuestra página en trozos y cachearlos de manera independiente, en función del intervalo de actualización de los mismos, un proceso conocido como Edge Side Includes (ESI) y que veremos a lo largo del artículo.
Instalación y configuración
La instalación de Varnish es un proceso tan simple como brew install varnish si usamos homebrew en mac. Una vez instalado, para ejecutarlo usamos el siguiente comando:
sudo /usr/local/opt/varnish/sbin/varnishd -n /usr/local/var/varnish -f /usr/local/etc/varnish/default.vcl -s malloc,1G -T 127.0.0.1:2000 -a 0.0.0.0:8082
pasando como parámetros las siguientes opciones:
- Ruta del fichero de configuración
- Puerto de registro de log
- Máximo consumo de RAM
- Puerto de escucha (en nuestro caso el 8082)
El fichero de configuración default.vcl, para nuestro ejemplo, contiene las siguientes directivas:
backend default {
.host = "127.0.0.1";
.port = "8087";
}
sub vcl_recv {
if (req.http.cookie) {
unset req.http.cookie;
}
}
sub vcl_fetch {
if (req.url == "/layout/*") {
set beresp.ttl = 2m; /* Sets a one minute TTL on */
} else {
set beresp.do_esi = true; /* Do ESI processing */
set beresp.ttl = 30s; /* Sets the TTL on the HTML above */
}
unset beresp.http.set-cookie;
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
Los comandos que hemos implementado son:
- backend: Permite especificar qué servidores estarán funcionando al otro lado, se puede especificar una IP o un nombre DNS.
-
vcl_recv: En el caso de ASP.net, se genera una cookie de sesión al acceder al sitio, lo cual no siempre es lo deseable. En este ejemplo se borran todas las que provienen del servidor, de manera que se pueda cachear la página.
-
vcl_fetch: Se ejecuta cuando obtenemos un elemento desde el servidor, y en este caso especificamos que si estamos solicitando algún elemento de «layout» como el menú o la cabecera, ha de cachearlo durante 2 minutos, en caso contrario configuramos para que haga el procesamiento ESI (es decir, que intente unir los pedazos) y cachee el conjunto durante 30 segundos.
-
vcl_deliver: Nos permite editar la cabecera HTTP para saber si ha podido resolver el elemento en la caché o si ha tenido que acceder al servidor de aplicaciones.
Cacheando voy, cacheando vengo:
En este ejemplo hemos usado una plantilla de bootstrap que tiene el siguiente aspecto dentro de una aplicación prácticamente vacía en MVC5:

Lo primero que podemos apreciar son tres zonas claramente diferenciadas, una cabecera, una barra lateral, y, finalmente, un área de contenido principal.
Estructura
Para mostrar la página, empleamos un controlador muy sencillo que genera una espera de 3 segundos para simular carga del servidor y almacena la fecha de generación en el ViewData:
public class HomeController : Controller
{
public ActionResult Index ()
{
ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
System.Threading.Thread.Sleep (3000);
return View ("Index");
}
}
De manera adicional y para poder aplicar ESI, necesitaremos dividir nuestra vista en tres vistas diferentes que correspondan a acciones diferentes del controlador, que serán:
- / <- que genera el contenido de la página
- /layout/header <- que genera la cabecera
- /layout/sidebar <- que genera la barra lateral
Las dos acciones del controlador Layout son idénticas, como muestra el código:
public class LayoutController : Controller
{
public ActionResult Sidebar()
{
ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
System.Threading.Thread.Sleep (3000);
return View ("Sidebar");
}
public ActionResult Header()
{
ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
System.Threading.Thread.Sleep (3000);
return View ("Header");
}
}
Nótese que por cada acción hemos agregado otros 3 segundos de retraso, teniendo un total de 9 segundos en el caso peor.
Para poder activar el ESI en las vistas, usamos etiquetas HTML que tienen el siguiente formato:
<esi:include src="/layout/sidebar" onerror="continue"/>
Además, a modo de prueba, podemos agregar algo de código javascript que se ejecute una vez cargada la página para apreciar la diferencia entre hora de generación y hora de renderización. Si todo ha ido bien, cuando carguemos por primera vez nuestra caché, se encontrará vacía, y sufriremos la espera de los 9 segundos.
En la imagen, G muestra la hora de generación en el servidor y R la hora de renderización en el cliente:

Si volvemos a acceder, comprobaremos que el contenido de la página ya se ha cacheado, que las fechas de generación y de ejecución distan más de tres segundos, y que el inspector nos muestra el tiempo total de acceso a la página (700 milisegundos aprox):

Conclusiones
Ha sido una prueba de concepto muy interesante, el hecho de descubrir que podemos tener nuestra web dividida en secciones con cachés diferentes nos da mucha movilidad al hacer portales con información dinámica. Varnish es una herramienta suficientemente compleja para ser tomada en serio y requiere que estudiemos a fondo sus características antes aplicarla a un sistema en producción.
Lecturas adicionales y enlaces