sql >> Base de Datos >  >> NoSQL >> Redis

Almacenamiento en caché en Django con Redis

El rendimiento de la aplicación es vital para el éxito de su producto. En un entorno donde los usuarios esperan tiempos de respuesta del sitio web de menos de un segundo, las consecuencias de una aplicación lenta se pueden medir en dólares y centavos. Incluso si no está vendiendo nada, las cargas rápidas de página mejoran la experiencia de visitar su sitio.

Todo lo que sucede en el servidor entre el momento en que recibe una solicitud y el momento en que devuelve una respuesta aumenta la cantidad de tiempo que lleva cargar una página. Como regla general, cuanto más procesamiento pueda eliminar en el servidor, más rápido funcionará su aplicación. Almacenar en caché los datos después de que se hayan procesado y luego servirlos desde el caché la próxima vez que se soliciten es una forma de aliviar el estrés en el servidor. En este tutorial, exploraremos algunos de los factores que atascan su aplicación y demostraremos cómo implementar el almacenamiento en caché con Redis para contrarrestar sus efectos.

Bono Gratis: Haga clic aquí para obtener acceso a una Guía de recursos de aprendizaje de Django (PDF) gratuita que le muestra consejos y trucos, así como las trampas comunes que debe evitar al crear aplicaciones web de Python + Django.


¿Qué es Redis?

Redis es un almacén de estructura de datos en memoria que se puede utilizar como motor de almacenamiento en caché. Dado que mantiene los datos en la RAM, Redis puede entregarlos muy rápidamente. Redis no es el único producto que podemos usar para el almacenamiento en caché. Memcached es otro sistema popular de almacenamiento en caché en memoria, pero muchas personas están de acuerdo en que Redis es superior a Memcached en la mayoría de las circunstancias. Personalmente, nos gusta lo fácil que es configurar y usar Redis para otros fines, como Redis Queue.



Cómo empezar

Hemos creado una aplicación de ejemplo para presentarle el concepto de almacenamiento en caché. Nuestra aplicación utiliza:

  • Django (v1.9.8)
  • Barra de herramientas de depuración de Django (v1.4)
  • Django-redis (v4.4.3)
  • Redis (v3.2.0)

Instalar la aplicación

Antes de clonar el repositorio, instale virtualenvwrapper, si aún no lo tiene. Esta es una herramienta que le permite instalar las dependencias específicas de Python que necesita su proyecto, lo que le permite orientar las versiones y bibliotecas requeridas por su aplicación de forma aislada.

A continuación, cambie los directorios a donde guarda los proyectos y clone el repositorio de aplicaciones de ejemplo. Una vez hecho esto, cambie los directorios al repositorio clonado y luego cree un nuevo entorno virtual para la aplicación de ejemplo usando mkvirtualenv comando:

$ mkvirtualenv django-redis
(django-redis)$

NOTA: Creando un entorno virtual con mkvirtualenv también lo activa.

Instale todas las dependencias de Python requeridas con pip y luego revisa la siguiente etiqueta:

(django-redis)$ git checkout tags/1

Termine de configurar la aplicación de ejemplo creando la base de datos y rellenándola con datos de muestra. Asegúrese de crear un superusuario también, para que pueda iniciar sesión en el sitio de administración. Siga los ejemplos de código a continuación y luego intente ejecutar la aplicación para asegurarse de que funciona correctamente. Visite la página de administración en el navegador para confirmar que los datos se han cargado correctamente.

(django-redis)$ python manage.py makemigrations cookbook
(django-redis)$ python manage.py migrate
(django-redis)$ python manage.py createsuperuser
(django-redis)$ python manage.py loaddata cookbook/fixtures/cookbook.json
(django-redis)$ python manage.py runserver

Una vez que tenga la aplicación Django ejecutándose, vaya a la instalación de Redis.



Instalar Redis

Descargue e instale Redis siguiendo las instrucciones proporcionadas en la documentación. Alternativamente, puede instalar Redis usando un administrador de paquetes como apt-get o cerveza casera dependiendo de su sistema operativo.

Ejecute el servidor Redis desde una nueva ventana de terminal.

$ redis-server

A continuación, inicie la interfaz de línea de comandos (CLI) de Redis en una ventana de terminal diferente y pruebe que se conecta al servidor de Redis. Usaremos la CLI de Redis para inspeccionar las claves que agregamos al caché.

$ redis-cli ping
PONG

Redis proporciona una API con varios comandos que un desarrollador puede usar para actuar en el almacén de datos. Django usa django-redis para ejecutar comandos en Redis.

Mirando nuestra aplicación de ejemplo en un editor de texto, podemos ver la configuración de Redis en settings.py expediente. Definimos un caché por defecto con el CACHES configuración, usando un django-redis incorporado caché como nuestro backend. Redis se ejecuta en el puerto 6379 de forma predeterminada y apuntamos a esa ubicación en nuestra configuración. Una última cosa a mencionar es que django-redis agrega nombres de clave con un prefijo y una versión para ayudar a distinguir claves similares. En este caso, hemos definido el prefijo como "ejemplo".

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient"
        },
        "KEY_PREFIX": "example"
    }
}

NOTA :Aunque hemos configurado el backend de caché, ninguna de las funciones de vista ha implementado el almacenamiento en caché.




Rendimiento de la aplicación

Como mencionamos al comienzo de este tutorial, todo lo que hace el servidor para procesar una solicitud ralentiza el tiempo de carga de la aplicación. La sobrecarga de procesamiento de ejecutar la lógica empresarial y las plantillas de representación puede ser significativa. La latencia de la red afecta el tiempo que lleva consultar una base de datos. Estos factores entran en juego cada vez que un cliente envía una solicitud HTTP al servidor. Cuando los usuarios inician muchas solicitudes por segundo, los efectos en el rendimiento se notan a medida que el servidor trabaja para procesarlas todas.

Cuando implementamos el almacenamiento en caché, dejamos que el servidor procese una solicitud una vez y luego la almacenamos en nuestro caché. A medida que nuestra aplicación recibe solicitudes de la misma URL, el servidor extrae los resultados de la memoria caché en lugar de procesarlos de nuevo cada vez. Por lo general, establecemos un tiempo de vida en los resultados almacenados en caché, de modo que los datos se puedan actualizar periódicamente, lo cual es un paso importante a implementar para evitar entregar datos obsoletos.

Debería considerar almacenar en caché el resultado de una solicitud cuando se cumplan los siguientes casos:

  • representar la página implica muchas consultas a la base de datos y/o lógica comercial,
  • la página es visitada con frecuencia por sus usuarios,
  • los datos son los mismos para todos los usuarios,
  • y los datos no cambian con frecuencia.

Empiece por medir el rendimiento

Comience probando la velocidad de cada página en su aplicación comparando qué tan rápido su aplicación devuelve una respuesta después de recibir una solicitud.

Para lograr esto, estaremos llenando cada página con una ráfaga de solicitudes utilizando loadtest, un generador de carga HTTP, y luego prestando mucha atención a la tasa de solicitudes. Visite el enlace de arriba para instalar. Una vez instalado, pruebe los resultados contra el /cookbook/ Ruta URL:

$ loadtest -n 100 -k  http://localhost:8000/cookbook/

Tenga en cuenta que estamos procesando alrededor de 16 solicitudes por segundo:

Requests per second: 16

Cuando observamos lo que está haciendo el código, podemos tomar decisiones sobre cómo realizar cambios para mejorar el rendimiento. La aplicación realiza 3 llamadas de red a una base de datos con cada solicitud a /cookbook/ , y lleva tiempo que cada llamada abra una conexión y ejecute una consulta. Visite el /cookbook/ URL en su navegador y expanda la pestaña Barra de herramientas de depuración de Django para confirmar este comportamiento. Busque el menú con la etiqueta "SQL" y lea el número de consultas:

libro de recetas/servicios.py

from cookbook.models import Recipe


def get_recipes():
    # Queries 3 tables: cookbook_recipe, cookbook_ingredient,
    # and cookbook_food.
    return list(Recipe.objects.prefetch_related('ingredient_set__food'))

libro de recetas/views.py

from django.shortcuts import render
from cookbook.services import get_recipes


def recipes_view(request):
    return render(request, 'cookbook/recipes.html', {
        'recipes': get_recipes()
    })

La aplicación también genera una plantilla con una lógica potencialmente costosa.

<html>
<head>
  <title>Recipes</title>
</head>
<body>
{% for recipe in recipes %}
  <h1>{{ recipe.name }}</h1>
    <p>{{ recipe.desc }}</p>
  <h2>Ingredients</h2>
  <ul>
    {% for ingredient in recipe.ingredient_set.all %}
    <li>{{ ingredient.desc }}</li>
    {% endfor %}
  </ul>
  <h2>Instructions</h2>
    <p>{{ recipe.instructions }}</p>
{% endfor %}
</body>
</html>


Implementar almacenamiento en caché

Imagine el número total de llamadas de red que realizará nuestra aplicación cuando los usuarios comiencen a visitar nuestro sitio. Si 1000 usuarios acceden a la API que recupera recetas de libros de cocina, nuestra aplicación consultará la base de datos 3000 veces y se generará una nueva plantilla con cada solicitud. Ese número solo crece a medida que escala nuestra aplicación. Afortunadamente, esta vista es una gran candidata para el almacenamiento en caché. Las recetas en un libro de cocina rara vez cambian, si es que alguna vez lo hacen. Además, dado que la visualización de libros de cocina es el tema central de la aplicación, se garantiza que la API que recupera las recetas se llamará con frecuencia.

En el siguiente ejemplo, modificamos la función de vista para usar el almacenamiento en caché. Cuando se ejecuta la función, comprueba si la clave de visualización está en la memoria caché. Si la clave existe, la aplicación recupera los datos del caché y los devuelve. Si no, Django consulta la base de datos y luego guarda el resultado en el caché con la clave de vista. La primera vez que se ejecuta esta función, Django consultará la base de datos y generará la plantilla, y luego también realizará una llamada de red a Redis para almacenar los datos en el caché. Cada llamada posterior a la función omitirá por completo la base de datos y la lógica empresarial y consultará la memoria caché de Redis.

ejemplo/configuración.py

# Cache time to live is 15 minutes.
CACHE_TTL = 60 * 15

libro de recetas/views.py

from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.shortcuts import render
from django.views.decorators.cache import cache_page
from cookbook.services import get_recipes

CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)


@cache_page(CACHE_TTL)
def recipes_view(request):
    return render(request, 'cookbook/recipes.html', {
        'recipes': get_recipes()
    })

Tenga en cuenta que hemos agregado @cache_page() decorador a la función de vista, junto con un tiempo para vivir. Visite el /cookbook/ URL nuevamente y examine la barra de herramientas de depuración de Django. Vemos que se hacen 3 consultas a la base de datos y 3 llamadas a la caché para buscar la clave y luego guardarla. Django guarda dos claves (1 clave para el encabezado y 1 clave para el contenido de la página representada). Vuelva a cargar la página y observe cómo cambia la actividad de la página. La segunda vez, se realizan 0 llamadas a la base de datos y 2 llamadas al caché. ¡Nuestra página ahora se está sirviendo desde el caché!

Cuando volvemos a ejecutar nuestras pruebas de rendimiento, vemos que nuestra aplicación se carga más rápido.

$ loadtest -n 100 -k  http://localhost:8000/cookbook/

El almacenamiento en caché mejoró la carga total y ahora estamos resolviendo 21 solicitudes por segundo, que son 5 más que nuestra línea de base:

Requests per second: 21


Inspección de Redis con la CLI

En este punto, podemos usar la CLI de Redis para ver qué se almacena en el servidor de Redis. En la línea de comandos de Redis, ingrese las keys * comando, que devuelve todas las claves que coinciden con cualquier patrón. Debería ver una clave llamada "ejemplo:1:views.decorators.cache.cache_page". Recuerda, “ejemplo” es el prefijo de nuestra clave, “1” es la versión y “views.decorators.cache.cache_page” es el nombre que Django le da a la clave. Copie el nombre de la clave e ingréselo con get dominio. Debería ver la cadena HTML procesada.

$ redis-cli -n 1
127.0.0.1:6379[1]> keys *
1) "example:1:views.decorators.cache.cache_header"
2) "example:1:views.decorators.cache.cache_page"
127.0.0.1:6379[1]> get "example:1:views.decorators.cache.cache_page"

NOTA: Ejecute el flushall en la CLI de Redis para borrar todas las claves del almacén de datos. Luego, puede volver a ejecutar los pasos de este tutorial sin tener que esperar a que caduque la memoria caché.




Resumen

El procesamiento de solicitudes HTTP es costoso y ese costo se suma a medida que su aplicación crece en popularidad. En algunos casos, puede reducir en gran medida la cantidad de procesamiento que realiza su servidor implementando el almacenamiento en caché. Este tutorial abordó los conceptos básicos del almacenamiento en caché en Django con Redis, pero solo rozó la superficie de un tema complejo.

La implementación del almacenamiento en caché en una aplicación robusta tiene muchas trampas y trampas. Controlar lo que se almacena en caché y por cuánto tiempo es difícil. La invalidación de caché es una de las cosas difíciles en informática. Garantizar que solo los usuarios previstos puedan acceder a los datos privados es un problema de seguridad y debe manejarse con mucho cuidado al almacenar en caché.

Bono Gratis: Haga clic aquí para obtener acceso a una Guía de recursos de aprendizaje de Django (PDF) gratuita que le muestra consejos y trucos, así como las trampas comunes que debe evitar al crear aplicaciones web de Python + Django.

Juega con el código fuente de la aplicación de ejemplo y, a medida que continúas desarrollando con Django, recuerda tener siempre en cuenta el rendimiento.