El libro de Django 1.0

13.5. Caches upstream

Este capítulo se ha enfocado en la cache de tus propios datos. Pero existe otro tipo de cache que es muy importante para los desarrolladores web: la cache realizada por los upstream. Estos son sistemas que colocan en cache páginas aún antes de que estas sean pedidas a tu sitio Web.

Aquí hay algunos ejemplos de caches para upstream:

  • Tu ISP puede tener en cache algunas páginas, si tu pides una página de http://example.com/, tu ISP te enviará la página sin tener que acceder a example.com directamente. Los responsables de example.com no tienen idea que esto pasa; el ISP se coloca entre example.com y tu navegador, manejando todo lo que se refiera a cache transparentemente.
  • Tu sitio en Django puede colocarse detrás de un cache proxy, como Squid Web Proxy Cache (http:://www.squid-cache.org/), que coloca en cache páginas para un mejor rendimiento. En este caso, cada pedido será controlado por el proxy antes que nada, y será pasado a tu aplicación sólo si es necesario.
  • Tu navegador también pone páginas en un cache. Si una página Web envía unos encabezados apropiados, tu navegador usará su copia de la cache local para los siguientes pedidos a esa página, sin siquiera hacer nuevamente contacto con la página web para ver si esta ha cambiado.

La cache de upstream es un gran beneficio, pero puede ser peligroso. El contenido de muchas páginas Web pueden cambiar según la autenticación que se haya realizado u otras variables, y los sistemas basados en almacenar en cache según la URL pueden exponer datos incorrectos o delicados a diferentes visitantes de esas páginas.

Por ejemplo, digamos que manejas un sistema de e-mail basado en Web, el contenido de la "bandeja de entrada" obviamente depende de que usuario esté logueado. Si el ISP hace caching de tu sitio ciegamente, el primer usuario que ingrese al sistema compartirá su bandeja de entrada, que está en cache, con los demás usuarios del sistema. Eso, definitivamente no es bueno.

Afortunadamente, el protocolo HTTP provee una solución a este problema. Existen un número de encabezados HTTP que indican a las cache de upstream que diferencien sus contenidos de la cache dependiendo de algunas variables, y para que algunas páginas particulares no se coloquen en cache. Veremos algunos de estos encabezados en las secciones que siguen.

13.5.1. Usar el encabezado Vary

El encabezado Vary define cuales encabezados debería tener en cuenta un sistema de cache cuando construye claves de su cache. Por ejemplo, si el contenido de una página Web depende de las preferencias de lenguaje del usuario, se dice que la página "varía según el lenguaje".

Por omisión, el sistema de cache de Django crea sus claves de cache usando la ruta que se ha requerido (p.e.: "/stories/2005/jun/23/bank_robbed/"). Esto significa que cada pedido a esa URL usará la misma versión de cache, independientemente de las características del navegador del cliente, como las cookies o las preferencias del lenguaje. Sin embargo, si esta página produce contenidos diferentes basándose en algunas cabeceras del request — como las cookies, el lenguaje, o el navegador — necesitarás usar el encabezado Vary para indicarle a la cache que esa página depende de esas cosas.

Para hacer esto en Django, usa el decorador vary_on_headers como sigue:

from django.views.decorators.vary import vary_on_headers

# Python 2.3 syntax.
def my_view(request):
    # ...
my_view = vary_on_headers(my_view, 'User-Agent')

# Python 2.4+ decorator syntax.
@vary_on_headers('User-Agent')
def my_view(request):
    # ...

En este caso, el mecanismo de cache (como middleware) colocará en cache una versión distinta de la página para cada tipo de user-agent.

La ventaja de usar el decorador vary_on_headers en vez de fijar manualmente el encabezado Vary (usando algo como response['Vary'] = 'user-agent') es que el decorador agrega al encabezado Vary (el cual podría ya existir), en vez de fijarlo desde cero y potencialmente sobrescribir lo que ya había ahí.

Puedes pasar múltiples encabezados a vary_on_headers():

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    # ...

Esto le dice a la cache de upstream que diferencie ambos, lo que significa que cada combinación de una cookie y un navegador obtendrá su propio valor en cache. Por ejemplo, un pedido con navegador Mozilla y una cookie con el valor foo=bar será considerada diferente a un pedido con el navegador Mozilla y una cookie con el valor foo=ham.

Como las variaciones con las cookies son tan comunes existe un decorador vary_on_cookie. Las siguientes dos vistas son equivalentes:

@vary_on_cookie
def my_view(request):
    # ...

@vary_on_headers('Cookie')
def my_view(request):
    # ...

El encabezado que le pasas a vary_on_headers no diferencia mayúsculas de minúsculas; "User-Agent" es lo mismo que "user-agent".

También puedes usar django.utils.cache.patch_vary_headers como función de ayuda. Esta función fija o añade al Vary header, por ejemplo:

from django.utils.cache import patch_vary_headers

def my_view(request):
    # ...
    response = render_to_response('template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers obtiene una instancia de HttpResponse como su primer argumento y una lista/tupla de nombres de encabezados, sin diferenciar mayúsculas de minúsculas, como su segundo argumento.

13.5.2. Otros Encabezados de cache

Otro problema con la cache es la privacidad de los datos y donde deberían almacenarse los datos cuando se hace un vuelco de la cache.

El usuario generalmente se enfrenta con dos tipos de cache: su propia cache de su navegador (una cache privada) y la cache de su proveedor (una cache pública). Una cache pública es usada por múltiples usuarios y controlada por algunos otros. Esto genera un problema con datos sensibles--no quieres que, por ejemplo, el número de tu cuenta bancaria sea almacenado en una cache pública. Por lo que las aplicaciones Web necesitan una manera de indicarle a la cache cuales datos son privados y cuales son públicos.

La solución es indicar que la copia en cache de una página es "privada". Para hacer esto en Django usa el decorador de vista cache_control:

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    # ...

Este decorador se encarga de enviar los encabezados HTTP apropiados detrás de escena.

Existen otras pocas maneras de controlar los parámetros de cache. Por ejemplo, HTTP permite a las aplicaciones hacer lo siguiente:

  • Definir el tiempo máximo que una página debe estar en cache.
  • Especificar si una cache debería comprobar siempre la existencia de nuevas versiones, entregando unicamente el contenido de la cache cuando no hubiesen cambios. (Algunas caches pueden entregar contenido aun si la página en el servidor ha cambiado, simplemente porque la copia en cache todavía no ha expirado.)

En Django, utiliza el decorador cache_control para especificar estos parámetros de la cache. En el siguiente ejemplo, cache_control le indica a la cache revalidarse en cada acceso y almacenar versiones en cache hasta 3.600 segundos:

from django.views.decorators.cache import cache_control
@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
    ...

Cualquier directiva Cache-Control de HTTP válida es válida en cache_control(). Aquí hay una lista completa:

  • public=True
  • private=True
  • no_cache=True
  • no_transform=True
  • must_revalidate=True
  • proxy_revalidate=True
  • max_age=num_seconds
  • s_maxage=num_seconds

Truco Para una explicación de las directivas Cache-Control de HTTP, lea las especificaciones en http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.

El middleware de caching ya fija el encabezado max-age con el valor de CACHE_MIDDLEWARE_SETTINGS. Si utilizas un valor propio de max_age en un decorador cache_control, el decorador tendrá precedencia, y los valores del encabezado serán fusionados correctamente.