Soporte Asíncrono a partir de Django: Lo que Necesitas Saber

- 👤 Andrés Cruz

🇺🇸 In english

Django al igual que muchos otros frameworks web, son conocidos por su naturaleza sincrónica al momento de resolver las peticiones del cliente y no es hasta la versión 3.2 que proporciona un mecanismo asíncrono para tal fin. En este artículo, explicaremos qué significa esto y cómo afecta a las aplicaciones Django.

¿Qué es el Soporte Asíncrono en Django?

El soporte asíncrono en Django permite escribir vistas asíncronas (“async”), junto con una pila de solicitudes completamente habilitada para el modo asíncrono si estás usando un servidor de tipo ASGI (Asynchronous Server Gateway Interface) como el que usamos en Django Channels. Las vistas (controladores en el MVC) asíncronas seguirán funcionando bajo WSGI (Web Server Gateway Interface), pero con penalizaciones de rendimiento y sin la capacidad de tener solicitudes de larga duración eficientes.

Ventajas del Soporte Asíncrono

  1. Escalabilidad: ASGI permite que Django se escale horizontalmente distribuyendo la carga de trabajo entre múltiples procesos o máquinas. Esto es especialmente útil para aplicaciones con alta concurrencia.
  2. Solicitudes de Larga Duración: Las vistas asíncronas pueden manejar solicitudes de larga duración de manera eficiente, como conexiones persistentes o streaming lento.
  3. Concurrencia: Puedes utilizar características asíncronas, como solicitudes HTTP concurrentes, sin problemas.

Implementación de Vistas Asíncronas

Cualquier vista puede declararse como asíncrona utilizando la sintaxis async def. Para vistas basadas en funciones, declara toda la vista con async def. Para vistas basadas en clases, declara los manejadores de métodos HTTP (como get() y post()) como async def.

# Ejemplo de vista basada en función
from django.http import JsonResponse

async def mi_vista(request):
    # Lógica asíncrona aquí
    return JsonResponse({"mensaje": "Hola, mundo asíncrono!"})

El soporte asíncrono en Django es una característica poderosa que te permite mejorar el rendimiento y la escalabilidad de tus aplicaciones.

¿Debería utilizar el soporte asíncrono de Django?

Al igual que yo, es posible que hayas considerado usar Django si quisieras un framework con todas las funciones para un desarrollo web rápido.

Desafortunadamente, Django no es famoso por su rendimiento, tal vez debido a su servicio síncrono (Sync) predeterminado. 

La sincronización funciona mal porque un subproceso del servidor solo puede atender una solicitud a la vez. Es importante destacar que las solicitudes de E/S bloquean el hilo. El rendimiento se puede aumentar agregando subprocesos del servidor, pero los subprocesos del servidor Python ocupan casi la misma cantidad de memoria que un proceso del servidor Python. Además, el uso de un servidor asíncrono evita que su equipo tenga que pensar en optimizar el parámetro de número de subprocesos. Si bien la memoria es uno de los recursos más baratos, no es óptimo para escalar costos innecesarios. Si la memoria del servidor no tiene un impacto sustancial en el precio, es posible que pueda obtener costos mucho más bajos si utiliza la tecnología sin servidor: 

https://nalkhish.medium.com/hosting-very-low-traffic-web-apps-for-cheap -en-aws-3-año-66f748cd04cf

El diseño asincrónico (Async) se agregó en Django 3.0 (https://docs.djangoproject.com/en/3.0/topics/async/) y no se menciona en la introducción. Es una ocurrencia de último momento mencionada como último tema sobre el uso de Django (https://docs.djangoproject.com/en/5.0/topics/).

Async puede superar a Sync porque un único subproceso del servidor Async puede atender varias solicitudes a la vez. Cuando una tarea espera una corrutina, devuelve el control a otros subprocesos. Este rendimiento voluntario (1) le da al diseño asíncrono el nombre de multitarea colaborativa y (2) lo hace más eficiente que el cambio de control basado en reloj en subprocesos múltiples.

Entonces eso generó las preguntas:

  • ¿Cuánto más rendimiento puede obtener Django usando una configuración asincrónica?
  • ¿Importa lo que esté haciendo la ruta/punto final (con mucha CPU o con mucha IO)?
  • ¿Qué pasa si hay una latencia baja (el servidor está cerca) o una latencia alta (el servidor está lejos)?
    experimento

Configuración del subproceso del trabajador del servidor

  • Para Sync, se utilizó Gunicorn para activar 1 trabajador de Sync (https://docs.gunicorn.org/en/stable/design.html).
  • Para Async Limited, Gunicorn se utilizó para activar 1 trabajador de Uvicorn (https://www.uvicorn.org/deployment/)
  • Para Async Unlimited, se usó Gunicorn para activar 1 trabajador de Uvicorn (https://www.uvicorn.org/deployment/), pero postgres se configuró para permitir 1000 conexiones simultáneas (más de las 100 predeterminadas).

Sincronización vs asíncrono

Sync tenía un sRPS máximo más bajo que Async unlimited. Probablemente esto se deba a que los servidores asíncronos pueden manejar múltiples solicitudes a la vez y, por lo tanto, varias solicitudes finalizaron al mismo tiempo. Esto es sorprendente porque supuestamente asyncio no cambia de contexto a menos que llegue a una declaración de espera, que no existe en la ruta de la CPU. Esto debe investigarse más a fondo.
Sync tenía una dinámica predecible y tuvo mayores éxitos generales que Async. Esto es suficiente para garantizar el uso de Sync para servicios vinculados a la CPU.
Async Limited vs Async Unlimited: Async Unlimited tuvo un sRPS máximo más alto y éxitos generales que Async Limited.

Para la ruta de lectura vinculada a IO, esto probablemente se puede atribuir a que la base de datos es un cuello de botella porque estaba fallando.
Para la ruta de creación vinculada a IO, esto debe investigarse más a fondo ya que la base de datos no fallaba por Async Limited (consulte la figura complementaria)

Sincronización frente a asíncrono

Tanto para las rutas io_read como para io_create, Sync tuvo un rendimiento mucho menor que Async (para una latencia adicional de 200 ms, la diferencia en el rendimiento general fue 40 veces mayor para io_read y 230 veces mayor para io_create).
Es probable que esto se deba a que el subproceso de trabajo del servidor estaba esperando a que finalizaran las solicitudes de la base de datos antes de poder manejar la siguiente solicitud. Esta teoría está respaldada por la relación inversa entre la latencia y el sRPS máximo y el éxito general de Sync.

Conclusión

Las limitaciones se exploran más en el archivo Readme del repositorio https://github.com/nalkhish/Asynchronize-Django (la poca información que se analiza aquí, la ausencia de lógica de reintento y la ausencia de latencia entrante), pero este estudio compara suficientemente los métodos asincrónicos y Servicio Django sincrónico.

Si usa Django y tiene cargas vinculadas a la CPU, use Sync. De lo contrario, si las cargas están vinculadas a E/S, utilice Async. Probablemente aumentará más de 10 veces el rendimiento de sus trabajadores.

No investigué el uso de múltiples subprocesos sincrónicos porque tenía que preocuparme por optimizar la cantidad de subprocesos de acuerdo con los costos de memoria adicionales y la latencia experimentada por los usuarios.

Artículo original:

https://nalkhish.medium.com/should-you-use-django-asynchronous-support-f86ef611d29f

Acepto recibir anuncios de interes sobre este Blog.

El soporte asíncrono desde Django 3 permite vistas asíncronas y escalabilidad, mejorando el rendimiento de aplicaciones web, aprende más!

| 👤 Andrés Cruz

🇺🇸 In english