Mejorar tasa de acierto en caché de Varnish

La meta de Varnish es cachear contenido, por lo que uno de los objetivos principales del administrador de Varnish es estudiar la estructura de la web y la salida de los logs y estadísticas para modificar la configuración de forma que se aumenten en la medida de lo posible los hits en la caché y se reducan las consultas al backend HTTP.

Para eso, se presentan una serie de "trucos" de configuración que ayudarán en esta tarea.


Si Varnish encuentra cualquier tipo de cabecera "Authorization", hará un pass de la petición, puesto que no puede cachearla. En algunos casos, el servidor HTTP puede estar enviando este tipo de cabeceras en casos en que sí que queremos que Varnish realice cacheo. En ese caso, bastará con hacer un unset de dicha cabecera:

sub vcl_recv 
{

    # (...)
    if ( req.request == "GET" && 
         req.http.Authorization && 
         req.url ~ "^/static/" ) 
    {
        unset req.http.Authorization;
        unset req.http.cookie;
    }
    
    # (...)
}


En su configuración por defecto, Varnish se ve profundamente afectado por las Cookies:


  • Si el cliente HTTP manda una petición con Cookie, Varnish bypasseará la caché y acudirá directamente al backend HTTP.
  • Si el backend HTTP devuelve contenido con una cabecera Set-Cookie presente, Varnish no cacheará dicho objeto.


Este comportamiento de Varnish es conservador y es nuestra tarea adaptarlo vía código VCL a la realidad de nuestra Web. Por ejemplo, podemos alterar este comportamiento si:


  • Los navegadores cliente nos mandan una cookie también con las peticiones de contenido estático (para los cuales no tiene sentido dicha cookie).
  • Los navegadores cliente nos mandan la cookie de Google Analytics, que sólo tiene sentido cuando el navegador cliente accede al servidor de estadísticas remoto (no el nuestro).
  • Accedemos a una parte de la web que no requiere uso de cookies (ni siquiera de nuestra propia web).


En estos casos, lo normal es eliminar o manipular las Cookies enviadas por el cliente o por el servidor usando lógica VCL.


sub vcl_recv
{
    # (...)
    
    unset req.http.Cookie;
    
    # (...)
}


sub vcl_recv
{

    # Si la URL de la peticion contiene la regexp ^/images/, tratar
    # de devolverla desde la cache de varnish:
    if (req.url ~ "^/static/" ||
        req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|zip|exe)$" ) 
    {
        # Eliminamos primero las cookies que nos pueda mandar el cliente
        unset req.http.cookie;
        return(lookup);
    }
    
}


El siguiente código elimina las cookies de toda la web, menos de una parte de la misma (el área administrativa, por ejemplo, que requiere una cookie de sesión):

sub vcl_recv
{
    if ( !( req.url ~ ^/admin/) ) 
    {
        unset req.http.Cookie;
    }
}


Varnish permite sólo 2 opciones con las cookies: eliminarlas, o manipularlas con expresiones regulares.

En el siguiente ejemplo vemos cómo eliminar las cookies de Google Analytics aprovechando que empiezan todas ellas por un carácter de subrayado:

// Remove has_js and Google Analytics __* cookies.
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");

// Remove a ";" prefix, if present.
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");


El siguiente ejemplo, extraído de la documentación sobre cookies de Varnish, muestra cómo eliminar todas las cookies excepto 2 cookies de nombre COOKIE1 y COOKIE2:

sub vcl_recv 
{
    if (req.http.Cookie) 
    {
        set req.http.Cookie = ";" + req.http.Cookie;
        set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
        set req.http.Cookie = regsuball(req.http.Cookie, ";(COOKIE1|COOKIE2)=", "; \1=");
        set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");

        if (req.http.Cookie == "") 
        {
            remove req.http.Cookie;
        }
    }
}


Antes de explicar en qué afecta la compresión a Varnish, se requiere una "introducción" a cómo funciona la compresión entre cliente y servidor HTTP.

La compresión HTTP reduce el ancho de banda requerido para transmitir un objeto a costa de un pequeño consumo adicional de CPU en el servidor y en el cliente HTTP.

El mecanismo de compresión que utilizan el cliente y el servidor HTTP se negocia (el servidor es libre de ignorar la petición de compresión del cliente) y actualmente se hace uso principalmente de 2 algoritmos: deflate (pkzip, winzip) y gzip (gzip/gunzip).

El cliente HTTP (navegador) indica al servidor HTTP qué encodings soporta con cabecera Accept-Encoding:

# Algunos navegadores (espacio después de la coma)
Accept-Encoding: gzip, deflate

# Otros navegadores (sin espacio después de la coma)
Accept-Encoding: gzip,deflate 

El servidor indica entonces al cliente si va a hacer caso de la cabecera Accept-Encoding y lo indica al cliente con una cabecera Vary:

Vary: Accept-Encoding

Debido a que Explorer manda la cabecera de forma diferente al resto, se requiere el siguiente código VCL en vcl_recv() para gestionar el tipo de compresión, seleccionando gzip por defecto si está disponible:

    if (req.http.Accept-Encoding) 
    {
        if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|zip|exe)$") 
        {
            # No tiene sentido comprimir este tipo de contenidos:
            remove req.http.Accept-Encoding;
        } 
        elsif (req.http.Accept-Encoding ~ "gzip") 
        {
            set req.http.Accept-Encoding = "gzip";
        } 
        elsif (req.http.Accept-Encoding ~ "deflate" && 
               req.http.user-agent !~ "MSIE")
        {
            set req.http.Accept-Encoding = "deflate";
        }
        else 
        {
            # Algoritmo de compresion desconocido -> no comprimir
            remove req.http.Accept-Encoding;
        }
    }

Esto puede producir que tengamos hasta 3 versiones de un mismo objeto en la caché:

  • Comprimido con gzip.
  • Comprimido con deflate.
  • No comprimido.

Esto tiene impacto en la caché en cuanto a:

  • Triple ocupación en caché.
  • Más accesos al backend de los necesarios.

Para evitar esto, muchas configuraciones de Varnish deshabilitan totalmente la compresión a costa de un mayor consumo de ancho de banda:

sub vcl_recv
{
    # (...)
    
    unset req.http.Accept-Encoding;
    
    # (...)
}

Esto es recomendado en Varnish 2.x, que no soporta compresión. A partir de Varnish 3.0, se soporta la compresión con gzip, por lo que en esta y posteriores versiones, Varnish verifica si el backend soporta gzip y si es así reescribe las cabeceras Accept-Encoding a "gzip". En ese caso, los objetos sólo se almacenarán en la caché con formato gzip (a menos que el backend responda con un objeto sin compresión).

Podemos forzar a que Varnish comprima cualquier tipo de objeto de texto antes de guardarlo en caché así:

 sub vcl_fetch 
{
    if (beresp.http.content-type ~ "text") 
    {
        set beresp.do_gzip = true;
    }
}

Cabe destacar que la compresión gzip es compatible con ESI (Edge Server Includes) ya que Varnish hará descompresión al vuelo para el procesado ESI.

En cualquier caso, si el cliente no soporta gzip, Varnish se limita a servir al cliente lo que le transmite el backend. Si se sirve algún contenido desde la caché a dicho cliente, Varnish desempaquetará la página antes de mandársela al cliente.



Si nuestro servidor Web responde a varios Host Headers (ServerAlias'es), es posible que un mismo recurso aparezca varias veces en la caché debido a que el hash que se genera para el mismo depende de req.http.host.

Así, podemos tener una misma imagen por triplicado en caché si ha sido "accedida" desde www.dominio.com, dominio.com, dominio.es y www.dominio.es. Para evitar esto, podemos normalizar la petición a un único dominio (especialmente si no nos importa el resultado las estadísticas Web de servidor, ya que veremos todos los hits en el backend contra la dirección normalizada):

sub vcl_recv
{
    # Normalizar www.dominio.com y dominio.com como "dominio.com"
    if (req.http.host ~ "(?i)^(www.)?dominio.com") 
    {
        set req.http.host = "dominio.com";
    }
}

Esto reducirá la cantidad de datos en caché y permitirá almacenar así más objetos.


Podemos utilizar regsub() para reescribir URLs antes de enviarlas al backend HTTP:

if (req.http.host ~ "^(www.)?domain.com") 
{
   set req.url = regsub(req.url, "/static2/", "/oldstatic/");
}

También podemos usar esto en algunos casos para corregir algún error común:

sub vcl_recv 
{
    if (req.url == ”index.hmtl”) 
    {
        set req.url = ”index.html”;
    }
}


Aumentar el ratio de cacheos eliminando timestamps (usados para hacerla única) en la URL, cuando realmente no son necesarios (por ej. en un path de contenidos estáticos):

sub vcl_hash 
{
    if (req.url ~ "^/estaticos/") 
    {
        set req.url = regsub(req.url, "\?\d+", "");
    }
}

Aumentar el ratio de cacheos eliminando parámetros de query "?parametro" en la URL:

sub vcl_hash
{
    set req.hash += regsub(req.url, "\?.*", "");
    return(hash);
}


En ocasiones resulta imprescindible la utilización de varnishlog, varnishtop y varnishstats para determinar cómo mejorar la tasa de aciertos en caché.

Especialmente útil resultan las siguientes 2 opciones de varnishtop:

# Ver sólo las peticiones generadas por la IP de un cliente concreto:
varnishlog -c -m ReqStart:1.2.3.4

# Ver sólo las peticiones a backend generadas por la IP de un cliente
# gracias a que dicha IP aparece en la cabecera X-Forwarded-For:
varnishlog -c -m TxHeader:1.2.3.4

# Lista de URLs más pedidas por los clientes HTTP
varnishtop -i rxurl

# Lista de URLs más pedidas al backend HTTP (¡ESENCIAL!)
varnishtop -i txurl


Ver el apartado sobre ESI en esta misma Web.



<Volver a Página de VARNISH>

  • linux/servicios/varnish_mejorar_hits.txt
  • Última modificación: 04-05-2012 08:26
  • por sromero