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:

DjangoCeleryRedis

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:

  1. Fácil configuración con docker.
  2. Haciendo uso de RabbitMQ se pueden guardar las tareas pendientes por ejecutarse y retomarlas en caso de alguna falla.

Desventajas

  1. Require más dockers/servidores.
  2. 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:

  1. Fácil configuración.
  2. No require configurar nuevos docker/servidores.
  3. No require mantenimiento adicional.

Desventajas

  1. Tareas incompletas pueden perderse en caso de falla del sistema y un reinicio.
  2. 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!