================== Capítulo 15: Cache ==================
Los sitios Web estáticos, en los que las páginas son servidas directamente por el servidor Web, generan un gran escalamiento. Una gran desventaja en los sitios Web dinámicos, es precisamente eso, que son dinámicos. Cada vez que un usuario pide una página, el servidor realiza una serie de cálculos --consultas a una base de datos, renderizado de plantillas, lógica de negocio --para crear la página que el visitante finalmente ve. Esto es costoso desde el punto de vista del sobreprocesamiento.
Para la mayoría de las aplicaciones Web, esta sobrecarga no es gran cosa. La mayoría de las aplicaciones Web no son el washingtonpost o Slashdot; son de un tamaño pequeño a uno mediano, y con poco tráfico. Pero para los sitios con tráfico de medio a alto es esencial bajar lo más que se pueda el costo de procesamiento. He aquí cuando realizar un cache es de mucha ayuda.
Colocar en cache algo significa guardar el resultado de un cálculo costoso para que no se tenga que realizar el mismo la próxima vez. Aquí mostramos un pseudocódigo explicando cómo podría funcionar esto para una página Web dinámica:
dada una URL, buscar esa página en la cache si la página está en la cache: devolver la página en cache si no: generar la página guardar la página generada en la cache (para la próxima vez) devolver la página generada
Django incluye un sistema de cache robusto que permite guardar páginas dinámicas para que no tengan que ser recalculadas cada vez que se piden. Por conveniencia, Django ofrece diferentes niveles de granularidad de cache. Puedes dejar en cache el resultado de diferentes vistas, sólo las piezas que son difíciles de producir, o se puede dejar en cache el sitio entero.
Django también trabaja muy bien con caches de "downstream", tales como Squid y las caches de los navegadores. Estos son los tipos de cache que no controlas directamente pero a las cuales puedes proveerles algunas pistas (vía cabeceras HTTP) acerca de qué partes de tu sitio deben ser colocadas en cache y cómo.
Sigue leyendo para descubrir cómo usar el sistema de cache de Django. Cuando tu sitio se parezca cada vez más a Slashdot, estarás contento de entender este material.
El sistema de cache requiere sólo una pequeña configuración. A saber, tendrás que decirle donde vivirán los datos de tu cache, si es en una base de datos, en el sistema de archivos, o directamente en memoria. Esta es una decisión importante que afecta el rendimiento de la cache (si, algunos tipos de cache son más rápidos que otros). La cache en memoria generalmente será mucho más rápida que la cache en el sistema de archivos o la cache en una base de datos, porque carece del trabajo de tocar los mismos.
Tus preferencias acerca de la cache van en la variable CACHE
en el archivo
de configuración. A continuación daremos un recorrido por todos los valores y
configuraciones disponibles que puedes usar para configurar la CACHE
.
Por mucho, el más rápido y eficiente soporte nativo de cache para Django es memcached, el cual es un framework de cache basado enteramente en memoria, originalmente desarrollado para manejar grandes cargas en LiveJournal (http://www.livejournal.com/)y subsecuentemente por Danga Interactive (http://danga.com/). Es usado por sitios como Slashdot y Wikipedia para reducir el acceso a bases de datos e incrementar el rendimiento dramáticamente.
Memcached está disponible libremente para descargar. Corre como un demonio y se le asigna una cantidad específica de memoria RAM. Su característica principal es proveer una interfaz -- super-liviana-y-rápida para añadir, obtener y eliminar arbitrariamente datos en la cache. Todos los datos son guardados directamente en memoria, por lo tanto no existe sobrecarga de uso en una base de datos o en el sistema de archivos.
Después de haber instalado Memcached, es necesario que instales alguno de los adaptadores disponibles para usar Python con Memcached, los cuales no vienen incluidas con Django. Dichos adaptadores pueden ser python-memcached y/o pylibmc. Los cuales están disponibles como módulos de Python.
Para usar Memcached con Django, usa como BACKEND
a:
django.core.cache.backends.memcached.MemcachedCache
o
django.core.cache.backends.memcached.PyLibMCCache
(Dependiendo de
el adaptador que hayas elegido usar). Fija el valor de LOCATION
como
ip:puerto
, donde ip
es la dirección IP del demonio de Memcached y
puerto
es el puerto donde Memcached está corriendo o usa los valores
unix:path, donde path
es la ruta al archivo usado como socket en unix por
Memcached.
En el siguiente ejemplo, Memcached está corriendo en localhost (127.0.0.1) en el
puerto 11211, usando como dependencia python-memcached
:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } }
En el siguiente ejemplo, Memcache está disponible a través de el socket local
unix, que usa el archivo /tmp/memcached.sock
como socket, usando los enlaces
proporcionados por python-memcached
:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', } }
Una muy buena característica de Memcached es su habilidad de compartir la cache
en varios servidores. Esto significa que puedes correr demonios de Memcached en
diferentes máquinas, y el programa seguirá tratando el grupo de diferentes
máquinas como una sola cache, sin la necesidad de duplicar los valores de la
cache en cada máquina. Para sacar provecho de esta característica con Django,
incluye todas las direcciones de los servidores en LOCATION
, separados
por punto y coma.
En el siguiente ejemplo, la cache es compartida en varias instancias de Memcached en las direcciones IP 172.19.26.240 y 172.19.26.242, ambas en el puerto 11211:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
En el siguiente ejemplo, la cache es compartida en diferentes instancias de Memcached corriendo en las direcciones IP 172.19.26.240 (puerto 11211), 172.19.126.242 (puerto 11212) y 172.19.26.244 (puerto 11213):
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11212', '172.19.26.244:11213', ] } }
Una última observación acerca de Memcached es que la cache basada en memoria tiene una importante desventaja. Como los datos de la cache son guardados en memoria, serán perdidos si los servidores se caen. Más claramente, la memoria no es para almacenamiento permanente, por lo tanto no te quedes solamente con una cache basada en memoria. Sin duda, ninguno de los sistemas de cache de Django debe ser utilizado para almacenamiento permanente --son todos una solución para la cache, no para almacenamiento pero hacemos hincapié aquí porque la cache basada en memoria es únicamente para uso temporal.
Para usar una tabla de una base de datos como cache, tienes que crear una tabla en tu base de datos y apuntar el sistema de cache de Django a ella.
Primero, crea la tabla de cache corriendo el siguiente comando:
python manage.py createcachetable [nombre_tabla_cache]
Donde [nombre_tabla_cache]
es el nombre de la tabla a crear. Este nombre
puede ser cualquiera que desees, siempre y cuando sea un nombre válido para una
tabla y que no esté ya en uso en tu base de datos. Este comando crea una única
tabla en tu base de datos con un formato apropiado para el sistema de cache de
Django.
Una vez que se hayas creado la tabla, usa la propiedad LOCATION
como
LOCATION:nombre_tabla
, donde nombre_tabla
es el nombre de la tabla en la
base de datos y usa como BACKEND
django.core.cache.backends.db.DatabaseCache
En el siguiente ejemplo, el nombre de la tabla para el cache es
mi_tabla_cache
:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'mi_tabla_cache', } }
El sistema de cache usará la misma base de datos especificada en el archivo de configuración. Por lo que no podrás usar una base de datos diferente, a menos que la registres primero.
Para almacenar la cache en el sistema de archivos y almacenar cada valor de la
cache como un archivo separado, configura la propiedad BACKEND
usando
django.core.cache.backends.filebased.FileBasedCache
y especificando en
LOCATION
el directorio en tu sistema de archivos que debería almacenar los
datos de la cache.
Por ejemplo, para almacenar los datos de la cache en /var/tmp/django_cache
,
coloca lo siguiente:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', }
Si usas Windows, especifica la letra de la unidad al comienzo de la ruta de directorios de esta forma:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': 'c:/usuarios/temp', } }
La ruta de directorios, debe ser absoluta --debe comenzar con la raíz de tu sistema de archivos. No importa si colocas una barra al final de la misma.
Asegúrate que el directorio apuntado por esta propiedad exista y que pueda ser leído y escrito por el usuario del sistema usado por tu servidor Web, para ejecutarse.
Continuando con el ejemplo anterior, si tu servidor corre como usuario
apache
, asegúrate que el directorio /var/tmp/django_cache
exista y
pueda ser leído y escrito por el usuario apache
.
Cada valor de la cache será almacenado como un archivo separado conteniendo los
datos de la cache serializados ("pickled"), usando el módulo Python pickle
.
Cada nombre de archivo es una clave de la cache, modificado convenientemente
para que pueda ser usado por el sistema de archivos.
Si quieres usar la ventaja que otorga la velocidad de la cache en memoria, pero no tienes la capacidad de correr Memcached, puedes optar por el cache de memoria-local. Esta cache es por proceso y usa hilos-seguros, pero no es tan eficiente como Memcache dada su estrategia de bloqueo simple y reserva de memoria.
Para usarla, usa como BACKEND
a django.core.cache.backends.locmem.LocMemCache
por ejemplo:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unico-proceso' } }
El atributo LOCATION
de la cache es usada para identificar de forma
individual el almacenamiento de la memoria. Si utilizas únicamente un proceso
puedes omitir LOCATION
; sin embargo si utilizas más de uno, necesitas
asignar un nombre a al menos uno de los procesos para mantenerlos separados.
Observa que cada proceso tendrá su propia instancia de cache privada, lo cual significa que no es posible el proceso cruzado de cache. Esto obviamente también significa que la memoria local de cache no es particularmente muy eficiente, así que no es una buena opción para usar en ambientes de producción. Es recomendable solo para desarrollo.
A pesar de que Django incluye soporte para el uso de un buen número de sistemas de cache fuera de la caja, algunas veces puede que quieras usar un almacenamiento de cache personalizado, para fines específicos.
Para usar almacenamiento externo de cache con Django, usa la ruta de
importaciones de Python como BACKEND
y carga la configuración de la cache
así:
CACHES = { 'default': { 'BACKEND': 'ruta.a.backend', } }
Si estas construyendo tu propio sistema de cache, puedes usar el sistema de
almacenamiento de caches de Django como referencia para implementar el tuyo.
Puedes encontrar el código fuente en el directorio ubicado en:
django/core/cache/backends/
Finalmente, Django incluye una cache tonta formalmente llamada: "dummy" que no realiza cache; sólo implementa la interfaz de cache sin realizar ninguna acción.
Esta es útil cuando tienes un sitio en producción que usa mucho cache en varias
partes y en un entorno de desarrollo/prueba en el cual no quieres hacer cache.
En ese caso, usa BACKEND
como django.core.cache.backends.dummy.DummyCache
en el archivo de configuración para tu entorno de desarrollo, por ejemplo:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }
Como resultado de esto, tu entorno de desarrollo no usará cache, pero tu entorno de producción si lo hará.
Cada tipo de cache puede recibir argumentos adicionales para controlar el
comportamiento de la cache. Estos son dados como una clave adicional a la
configuración de CACHES
. Los argumentos válidos son los siguientes:
TIMEOUT
: El tiempo de vida por omisión, en segundos, que usará la cache. Este argumento tomará el valor de 300 segundos (5 minutos) si no se lo especifica.También puedes especificar
TIMEOUT
comoNone
, por defecto la clave de la cache nunca expira.OPTIONS
: Cualquier opción que se necesite pasar a la cache. La lista de opciones validas dependerá de cadabackend
, por lo que el almacenamiento de cache proporcionado por librerías de terceros, será pasado con sus opciones directamente bajo la cache de la librería.
Los almacenamientos de cache que implementan sus propias estrategias de selección (por ejemplo: en memoria, archivos y en base de datos) respetan las siguientes opciones:
MAX_ENTRIES
: Para la cache de memoria local, y la cache de base de datos, es el número máximo de entradas permitidas en la cache a partir del cual los valores más viejos serán eliminados. Tomará un valor de 300 si no se lo especifica.CULL_FREQUENCY
: La proporción de entradas que serán sacrificadas cuando la cantidad deMAX_ENTRIES
es alcanzada. La proporción real es1/CULL_FREQUENCY
, si quieres sacrificar la mitad de las entradas cuando se llegue a una cantidad deMAX_ENTRIES
colocaCULL_FREQUENCY=2
. Este argumento tomará un valor de 3 si no se especifica.Un valor de
0
paraCULL_FREQUENCY
significa que toda la cache será limpiada cuando se llegue a una cantidad de entradas igual aMAX_ENTRIES
. Esto hace que el proceso de limpieza de la cache sea mucho más rápido, con el costo de perder más datos de la cache. Este argumento tomará un valor de 3 si no se especifica.KEY_PREFIX
: Una cadena que automáticamente incluye (agrega por default) todas las claves de caches usadas por el servidor Django.VERSION
El número de versión de las claves de cache generadas por el servidor Django.KEY_FUNCTION
: Una cadena que contiene la ruta (usando el punto) a la función que define la forma en que está compuesta el prefijo, la versión y la clave, en la clave de la cache final
En este ejemplo, usamos un "archivo" como almacenamiento de cache (BACKEND), configurado con un valor de tiempo de 60 segundos (TIMEOUT) y con una capacidad máxima (MAX_ENTRIES) de 1000 ítems:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', 'TIMEOUT': 60, 'OPTIONS': { 'MAX_ENTRIES': 1000 } } }
Tanto los argumentos desconocidos, así como los valores inválidos de argumentos conocidos son ignorados silenciosamente.
Una vez que hayas especificado CACHE
, la manera más simple de usar
la cache es colocar en cache el sitio entero. Esto significa que cada página que
no tenga parámetros GET o POST será puesta en cache por un cierto período de
tiempo la primera vez que sean pedidas.
Para activar la cache por sitio solamente agrega
'django.middleware.cache.CacheMiddleware'
y
django.middleware.cache.FetchFromCacheMiddleware
a la propiedad
MIDDLEWARE_CLASSES
, como en el siguiente ejemplo:
MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', )
Nota:
El orden de MIDDLEWARE_CLASSES
importa. Mira la sección "Orden de
MIDDLEWARE_CLASSES" más adelante en este capítulo.
Luego, agrega las siguientes propiedades en el archivo de configuración de Django:
CACHE_MIDDLEWARE_ALIAS
: El nombre del alias para usar como almacenaje.CACHE_MIDDLEWARE_SECONDS
: El tiempo en segundos que cada página será mantenida en la cache.CACHE_MIDDLEWARE_KEY_PREFIX
: Si la cache es compartida a través de múltiples sitios usando la misma instalación Django, coloca esta propiedad como el nombre del sitio, u otra cadena que sea única para la instancia de Django, para prevenir colisiones. Usa una cadena vacía si no te interesa.
La cache middleware coloca en cache cada página que no tenga parámetros GET o POST. Esto significa que si un usuario pide una página y pasa parámetros GET en la cadena de consulta, o pasa parámetros POST, la cache middleware no intentará obtener la versión en cache de la página. Si intentas usar la cache por sitio ten esto en mente cuando diseñes tu aplicación; no uses URLs con cadena de consulta, por ejemplo, a menos que sea aceptable que tu aplicación no coloque en cache esas páginas.
Finalmente, nota que CacheMiddleware
automáticamente coloca unos pocos
encabezados en cada HttpResponse
:
- Coloca el encabezado
Last-Modified
con el valor actual de la fecha y hora cuando una página (aún no en cache) es requerida. - Coloca el encabezado
Expires
con el valor de la fecha y hora más el tiempo definido enCACHE_MIDDLEWARE_SECONDS
. - Coloca el encabezado
Cache-Control
para otorgarle una vida máxima a la página, como se especifica enCACHE_MIDDLEWARE_SECONDS
.
Una forma más granular de usar el framework de cache es colocar en cache la salida de las diferentes vistas. Esto tiene el mismo efecto que la cache por sitio (incluyendo la omisión de colocar en cache los pedidos con parámetros GET y POST). Se aplica a cualquier vista que tu especifiques, en vez de aplicarse al sitio entero.
Haz esto usando un decorador, que es un wrapper de la función de la vista que
altera su comportamiento para usar la cache. El decorador de cache por vista es
llamado cache_page
y se encuentra en el módulo
django.views.decorators.cache
, por ejemplo:
from django.views.decorators.cache import cache_page
def mi_vista(request, param):
# ...
mi_vista = cache_page(mi_vista, 60 * 15)
De otra manera, si estás usando alguna versión de Python, superior a la 2.7, puedes usar un decorador. El siguiente ejemplo es equivalente al anterior:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def mi_vista(request, param):
# ...
cache_page
recibe un único argumento: el tiempo de vida en segundos de la
cache. En el ejemplo anterior, el resultado de mi_vista()
estará en cache
unos 15 minutos. (toma nota de que lo hemos escrito como 60 * 15
para que
sea entendible. 60 * 15
será evaluado como 900
--que es igual a 15
minutos multiplicados por 60 segundos cada minuto.)
La cache por vista, como la cache por sitio, es indexada independientemente de la URL. Si múltiples URLs apuntan a la misma vista, cada URL será puesta en cache separadamente.
Continuando con el ejemplo de mi_vista
, si tu URLconf se ve como:
urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', mi_vista), ]
los pedidos a /foo/1/
y a /foo/23/
serán puestos en cache separadamente,
como es de esperar. Pero una vez que una misma URL es pedida (p.e.
/foo/23/
), los siguientes pedidos a esa URL utilizarán la cache.
cache_page
toma un argumento de clave opcional: llamado cache
, el cual
puede usarse directamente en el decorador especificando la cache (tomada de el
archivo de configuración de la variable CACHE
) para cachear la vista. Por
defecto, el cache para usar será especificado con cualquier cache que queramos,
por ejemplo:
@cache_page(60 * 15, cache="cache_especial")
def mi_vista(request):
...
algun_metodo()
También es posible sobrescribir el prefijo de la cache en la vista. el
decorador cache_page
toma un argumento de clave key_prefix
, el cual
trabaja de la misma forma que la configuración CACHE_MIDDLEWARE_KEY_PREFIX
en el middleware. Puede usarse de la siguiente forma:
@cache_page(60 * 15, key_prefix="sitio1")
def mi_vista(request):
...
algun_metodo()
Las dos configuraciones pueden ser combinadas. Si especificas cache
y
key_prefix
puedes traer todas las configuraciones en la petición usando
alias en la cache, solo que esto sobrescribirá el argumento key_prefix
.
Los ejemplos en la sección anterior incrustan [1] la cache en las vistas,
porque el decorador cache_page
modifica la función mi_vista
en la misma
vista. Este enfoque acopla tu vista con el sistema de cache, lo cual no es lo
ideal por varias razones. Por ejemplo, puede que quieras rehusar las funciones
de la vista en otro sitio sin cache, o puede que quieras distribuir las vistas
a gente que quiera usarlas sin que sean colocadas en la cache. La solución para
estos problemas es especificar la cache por vista en URLconf en vez de
especificarla junto a las vistas mismas.
Hacer eso es muy fácil: simplemente envuelve la función de la vista con
cache_page
cuando hagas referencia a ella en la URLconf. Aquí el URLconf
como estaba antes:
urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', mi_vista), ]
Ahora la misma cosa con mi_vista
envuelto con cache_page
:
from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(mi_vista)), ]
Si tomas este enfoque no olvides de importar cache_page
dentro de tu
URLconf.
Algunas veces, colocar en cache una página entera no te hace ganar mucho y es, de hecho, un inconveniente excesivo.
Quizás, por ejemplo, tu sitio incluye una vista cuyos resultados dependen de diversas consultas costosas, lo resultados de las cuales cambian en intervalos diferentes. En este caso, no sería ideal usar la página entera en cache que la cache por sitio o por vista ofrecen, porque no querrás guardar en cache todo el resultado (ya que los resultados cambian frecuentemente), pero querrás guardar en cache los resultados que rara vez cambian.
Para casos como este, Django expone una simple API de cache de bajo nivel, la
cual vive en el módulo django.core.cache
. Puedes usar la API de cache de
bajo nivel para almacenar los objetos en la cache con cualquier nivel de
granularidad que te guste. Puedes colocar en la cache cualquier objeto Python
que pueda ser serializado de forma segura: strings, diccionarios, listas de
objetos del modelo, y demás. (La mayoría de los objetos comunes de Python pueden
ser serializados; revisa la documentación de Python para más información acerca
de serialización). N.T.: pickling
Aquí vemos como importar la API:
>>> from django.core.cache import cache
La interfaz básica es set(key, value, timeout)
y get(key)
:
>>> cache.set('mi_clave', '¡Hola Mundo!', 30)
>>> cache.get('mi_clave')
'¡Hola Mundo!'
El argumento timeout
es opcional y obtiene el valor del argumento
timeout
de la variable CACHE
, explicado anteriormente, si no se lo
especifica.
Si el objeto no existe en la cache, o el sistema de cache no se puede alcanzar,
cache.get()
devuelve None
:
# Wait 30 seconds for 'mi_clave' to expire...
>>> cache.get('mi_clave')
None
>>> cache.get('otra_clave')
None
Te recomendamos que no almacenes el valor literal None
en la cache, porque
no podrás distinguir entre tu valor None
almacenado y el valor que devuelve
la cache cuando no encuentra un objeto.
cache.get()
puede recibir un argumento por omisión. Esto especifica qué
valor debe devolver si el objeto no existe en la cache:
>>> cache.get('mi_clave', 'ha expirado') 'ha expirado'
Para obtener múltiples valores de la cache de una sola vez, usa
cache.get_many()
. Si al sistema de cache le es posible, get_many()
tocará la cache sólo una vez, al contrario de tocar la cache por cada valor.
get_many()
devuelve un diccionario con todas las claves que has pedido que
existen en la cache y todavía no han expirado:
>>> cache.set('a', 1) >>> cache.set('b', 2) >>> cache.set('c', 3) >>> cache.get_many(['a', 'b', 'c']) {'a': 1, 'b': 2, 'c': 3}
Si una clave no existe o ha expirado, no será incluida en el diccionario. Lo siguiente es una continuación del ejemplo anterior:
>>> cache.get_many(['a', 'b', 'c', 'd']) {'a': 1, 'b': 2, 'c': 3}
Finalmente, puedes eliminar claves explícitamente con cache.delete()
. Esta
es una manera fácil de limpiar la cache para un objeto en particular:
>>> cache.delete('a')
cache.delete()
no tiene un valor de retorno, y funciona de la misma manera
si existe o no un valor en la cache.
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 downstream. 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 downstream:
- 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 downstream 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 downstream 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.
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
@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 mi_vista(request):
# ...
Esto le dice a la cache de downstream 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 mi_vista(request):
# ...
@vary_on_headers('Cookie')
def mi_vista(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 mi_vista(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.
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 mi_vista(request):
# ...
Este decorador se encarga de enviar los encabezados HTTP apropiados detrás de escena.
Nota que el control de configuraciones de cache "privado" y "publico" son
mutuamente excluyentes. El decorador se asegura que la directiva "publico" sea
removida si se encuentra configurado como "privado" (y viceversa). Un ejemplo
del uso de estas dos directivas, puede ser un sitio de un blog que ofrece
entradas públicas y privadas. Las entradas publicas pueden ser cacheadas en la
cache compartida. El siguiente código usa django.utils.cache.patch_cache_control()
para manualmente modificar el control de las cabeceras de la cache (Es
internamente llamado por el decorador cache_control
).
from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie
@vary_on_cookie
def lista_de entradas_blog(request):
if request.user.is_anonymous():
response = render_only_public_entries()
patch_cache_control(response, public=True)
else:
response = render_private_and_public_entries(request.user)
patch_cache_control(response, private=True)
return response
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 únicamente 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 mi_vista(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
Tip
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.
Si quieres usar cabeceras para desactivar el cache por completo, usa el decorador
never_cache
del paquete django.views.decorators.cache.never_cache
en
una vista, a la que le quieras agregar la cabecera, para asegurarte que la
respuesta no sea cacheada por el navegador u otros caches. Por ejemplo:
from django.views.decorators.cache import never_cache
@never_cache
def mi_vista(request):
# ...
Django incluye otras piezas de middleware que pueden ser de ayuda para optimizar el rendimiento de tus aplicaciones:
django.middleware.http.ConditionalGetMiddleware
agrega soporte para navegadores modernos para condicionar respuestas GET basadas en los encabezadosETag
yLas-Modified
.django.middleware.gzip.GZipMiddleware
comprime las respuestas para todos los navegadores modernos, ahorrando ancho de banda y tiempo de transferencia.
Si utilizas CacheMiddleware
, es importante colocarlas en el lugar
correcto dentro de la propiedad MIDDLEWARE_CLASSES
, porque el middleware
de cache necesita conocer los encabezados por los cuales cambiar el
almacenamiento en la cache.
Coloca el CacheMiddleware
después de cualquier middleware que pueda agregar
algo al encabezado Vary
, incluyendo los siguientes:
SessionMiddleware
, que agregaCookie
GZipMiddleware
, que agregaAccept-Encoding
LocaleMiddleware
que agregaAccept-Language
Django incluye un número de paquetes opcionales. Hemos cubierto algunos de los mismos: como el sistema de administración en él :doc:`capítulo 6<chapter06>`), el marco de sesiones/usuarios del :doc:`capítulo 14<chapter14>`).
Él :doc:`próximo capítulo<chapter16>` cubre el resto de paquetes incluidos en el modulo "contrib", que provee una cantidad interesante de herramientas disponibles; que pueden hacer más fácil tu vida, no querrás perderte ninguno de ellos.
[1] | N. del T.: hard-coded |