fitoria's blog

Posted Wed 16 January 2013

Django Suite IV: Hablemos un poco de caché.

Decía Phil Karlton "Solo hay dos cosas dificiles en las ciencias de la computación: invalidación de caché y nombrar cosas."

En esta entrega de la serie Django Suite - después de mas de un año de ausencia - hablaré sobre el framework de caché de Django.

Principios de caché

Cuando hacemos un sitio, especialmente si estamos comenzando no tomamos en cuenta el desempeño del sitio y la cantidad de tiempo de procesamiento por cada petición. El tiempo de procesamiento de un sitio está afectado por varios factores el mas importante la cantidad de consultas que realizamos a la base de dato por cada request, generalmente estas varían según la complejidad de nuestras vistas y el tipo de usuario (anónimo o autenticado) que tengamos en el sitio.

Para mejorar el desempeño del sitio se usa el caché que no es mas que un almacenamiento rápido, generalmente en RAM, que nos evita recaer en operaciones de cálculo pesadas como lo son hacer muchas consultas de base de datos la lógica de las vistas y el renderizado de las mismas en la plantilla que normalmente son realizadas haciendo lectura/escritura de disco duro que agrega mas tiempo a la receta.

Hacer caché de datos no es tan dificil, lo dificil es saber cuando los datos cacheados ya no son válidos y debemos de recalcular de nuevo para construir un nuevo caché, de ahí la frase del inicio del post.

Caché framework

Django contiene un framework de caché que abstrae las operaciones básicas, sin que nosotros tengamos que preocuparnos por el motor de caché que usemos, por defecto Django soporta Memcached, caché en base de datos, caché en el sistema de archivos y memoria local, siendo no muy recomendados estos últimos tres.

Pero como todo en Django la comunidad da soporte para otros motores de caché opensource muy buenos ademas de memcached, como por ejemplo:

Caché de vistas

Django nos provee con el decorador cache_page para hacer caché a una vista, el decorador lo que hace es lo siguiente.

Si la vista ha sido cargada por primera vez, hace todas las operaciones y la guarda en caché con tiempo de vencimiento designado en la llamada del decorador, caso contrario carga el resultado de la vista desde caché.

Ejemplo:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ....

El valor de 60 * 15 representa la duración de la validez del caché en segundos, en este caso 15 minutos, se acostumbra mucho para asignar valores de vencimiento poner multiplicaciones de la cantidad de segundos en un minuto (60) por el número de minutos deseado.

En el caso que la vista tuviera parámetros como un mi_vista(request, post_id) el caché ser haría de manera individual por cada valor de post_id que tengamos presente. Mas detalles de esto en la documentación oficial.

Caché de fragmentos de plantillas

Fragmentos plantillas también pueden ser cacheadas, esto es especialmente útil para fragmentos que repetimos en casi todas las páginas, por ejemplo elementos de menú dinámicos, columnas de 'últimas noticias' o 'últimos post' en caso de ser un blog.

Ejemplo:

{% load cache %}
{% cache 500 menu %}
    .. mi super menu de página ..
{% endcache %}

Igual acá la template tag de cache acepta como parámetros la duracion en segundos, el nombre del fragmento y el conjunto de variables que deseamos establecer en el cache.

Este conjuto de variables es tremendamente útil para hacer caché en diversas situaciones tales como si el usuario está o no autenticado, el lenguaje del navegador del usuario ( en caso que usemos la opción de internacionalización y localización del sitio ), valor de alguna varible que usemos que puede modificar el resultado de la plantilla entre otros.

Ejemplo en código fuente de la vida real acá.

Generalmente la mejor documentación es el código fuente, y para verdaderamente entender y jugar con esta template tag recomiendo que vean como está hecha en este archivo

Caché manual

Si queremos verdaderamente sacar provecho al framework de caché de django tenemos que tomar ventaja del API de caché manual. Los motores de caché se basan en almacenamiento de llave-valor (hashmap) o en lo que python seríá un equivalente al tipo diccionario.

El api de caché es bastante simple, podemos ver su uso en el siguiente código fuente:

#importamos el objeto caché
from django.core.cache import cache

#guardamos algo en el cache con expiración de 60 segundos
cache.set('a', 'un valor cacheado', 60)
#leemos algo desde cache
valor = cache.get('a')
#borramos 'a' del cache 
cache.delete('a')

Básicamente esa es el API de bajo nivel, si nosotros ponemos como valor de tiempo 0 el caché nunca se vencerá y tendremos que eliminarlo manualmente a posteriori.

Trucos con el caché manual.

El caché manual es tremendamente útil usándolo en conjunto con modelos. Un ejemplo sencillo y fácil de implementar es un menú dinámico, recientemente programé uno que se veía algo así:

#models.py
from django.db import models
from django.core.cache import cache 
 
class Menu(models.Model):
    titulo = models.CharField(max_length=50)
    url = models.URLField()
    peso = models.PositiveIntegerField(default=0,
            help_text="peso del elemento del menú, entre mayor sea el número mas hacia el fondo estará el elemento")
 
    def save(self, *args, **kwargs):
        #primero guardamos
        return_value = super(ElementoNavegacion, self).save(*args, **kwargs)
        #invalidamos el cache antiguo de menu
        cache.delete('menu')
        #creamos el nuevo cache refrescado
        cache.set('menu', Menu.objects.all(), 0)
        return return_value
 
    def __unicode__(self):
        return "%s (%s)" % (self.titulo, self.url)

Se creó un context manager para tener la lista del menú en todas las plantillas.

   #context manager para el menú.
   from django.core.cache import cache 
    
   from models import Menu
    
   def menu_context_manager(request):
       #probamos si esta el elemento en caché
       valor = cache.get('menu')
       #si no está leemos de la base de datos
       if not valor:
           valor = Menu.objects.all()
           #lo cacheamos
           cache.set('menu', valor, 0)

       return {'menu': valor}

Haciendo esto se ahorran un par de consultas a la base de datos y por ende la velocidad de carga del sitio aumenta.

Despedida

En resumen hemos visto una introducción al cache framework de Django, espero que les haya servido y tendré que hacer un post de continuación para exponer otros casos de uso del caché manual y trucos con el API, además del caché al lado del servidor de archivos estáticos y del lado del navegador.

Categories: personal, planeta, softwarelibre, programacion, python, django,

Comments