Configuración de tareas asíncronas en Django
Las aplicaciones web siemrpe tienen operationes que consumen mucho tiempo como enviar emails, preparar un reporte, hace operaciones pesadas de logica de negocio entre muchas otras. Hacer todas estas operaciones de forma sincrona hace que el usuario espere tiempos innecesarios para recibir una respuesta y nuestra aplicación se sentirá lenta.
Creando operationes asincronas nuestra aplicación va a responder más rapido y podemos responder un mensaje indicando que la operación solicitada esta en proceso mientas que en la cola de tareas vamos procesando la solicitud.
Como hacer esto en Django?
Celery y [Redis / RabbitMQ]
Django provee varais formas de hacer tareas asincronas! Una de estas formas es usar Celery, un broker de tareas y hacer uso de Redis o RabbitMQ como soporte de almacenamiento de mensajes de las tareas ejecutadas y por ejecutar. Esta configuración hace que tu sistema funcione de la siguiente forma:
Configuración en Docker
version: '2'
services:
postgres:
image: postgres:9.5
env_file: .env
django:
restart: always
build: ./django
links:
- postgres:postgres
- redis:redis
depends_on:
- postgres
env_file: .env
ports:
- "8081:8081"
command: ./run_django.sh
celery:
restart: always
build: ./django
links:
- redis:redis
env_file: .env
command: ./run_celery.sh
redis:
restart: always
image: redis:4.0.6
Configuración Celery
celery.py
from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings.prd")
app = Celery('myproject')
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
In settings.py add
# CELERY SETTINGS
BROKER_URL = 'redis://redis:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
Define las tareas de tus aplicaciones.
Por convención agrega un archivo tasks.py dentro de las aplicaciones que quieras definir operaciones asincronas como se muesta en el siguiente ejemplo.
from myproject.celery import app
@app.task
def my_heavy_process(var1, var2):
# Bussiness Logic!
# Send emails
# Generate report
# Grab lot informations and store result
Call your tasks!
from my_app.tasks import my_heavy_process
class MyView(View):
# bussiness logic here
my_heavy_process.delay(var1, var2)
# bussiness logic here
# response
Ventajas:
- Fácil configuración con docker.
- Haciendo uso de RabbitMQ se pueden guardar las tareas pendientes por ejecutarse y retomarlas en caso de alguna falla.
Desventajas
- Require más dockers/servidores.
- Require mayor ayuda de devops para el mantenimiento.
Asyncio
Existe otra forma de hace operaciones asincronas en Django usando Python3.5+ asyncio para dejar las operationes que queremos realizar de forma asincrona en el event loop.
import asyncio
loop = asyncio.get_event_loop()
def heavy_operation(args):
pass
class MyView(View):
# bussiness logic here
arguments = ['var1', 'var2']
loop.run_in_executor(None, heavy_operation, arguments)
# bussiness logic here
# response
Ventajas:
- Fácil configuración.
- No require configurar nuevos docker/servidores.
- No require mantenimiento adicional.
Desventajas
- Tareas incompletas pueden perderse en caso de falla del sistema y un reinicio.
- Sólo funciona en Python 3.5+ [Requiere actualización de python/django para proyectos antiguos]
Comentarios finales
Nuestas aplicaciones web deben responder rápido! Es por esto que es requerido tener en consideración esta opción para manejar operaciones pesadas. Aqui te presento algunas opciones escoge la que más te convenga.
Algunos links útiles!