Usar gevent cuando Gunicorn está esperando datos

Escrito el 2023-05-28 por Alberto Ferrer
Tiempo de lectura: 3 minuto(s)
Etiquetas: gevent gunicorn api python

Si estás utilizando gunicorn y realizas llamadas a API externas en tus vistas, es recomendable utilizar gevent para mejorar el rendimiento de tu aplicación. La clase gevent permite que el worker de gunicorn maneje múltiples solicitudes de forma asíncrona cuando está esperando los datos de una API externa. Algo similar a Nginx o Apache con mod_event.

Para usar gevent como worker en gunicorn, ejecuta el siguiente comando:

gunicorn --worker-class=gevent mysite:app

Supongamos que estás desarrollando un sitio web y necesitas mostrar artículos en una página. Para obtener los datos de los artículos, realizas una llamada a una función que maneja la lista de artículos en formato markdown y otros procesamientos. Sin embargo, debido a que te encuentras en México y el servidor de preparación de la API se encuentra en Canadá, la solicitud puede llevar tiempo en completarse. Como resultado, la página de artículos tarda en cargarse.

¿Qué sucede si muchas personas abren esa página al mismo tiempo? Para evaluar esta situación, creamos dos aplicaciones WSGI simples: una API externa y una aplicación que realiza llamadas a esta API.

A continuación, se muestra el código para la API externa:

import time

def external_app(environ, start_response):
    time.sleep(5)  
    data = b'Soy la API externa\n' 
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

Y aquí está el código para la aplicación que llama a la API:

import requests

def app_with_external_api(environ, start_response):
    if environ['PATH_INFO'] == '/slow':
        response = requests.get('http://localhost:8000') 
        data = response.content
    else:
        data = b'Rápido\n'  
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

Para iniciar la API externa, ejecuta el siguiente comando:

gunicorn externalapi:app

Y para iniciar la aplicación que llama a la API, ejecuta el siguiente comando:

gunicorn -b localhost:8080 mysite:app

Con estos argumentos, la aplicación "mysite" se ejecutará en un único worker. Este worker manejará cada solicitud de forma secuencial, lo que significa que si alguien está obteniendo la página "/slow", nadie más podrá obtener la página rápida hasta que se complete la respuesta.

Si deseas solucionar este problema, puedes agregar más workers a gunicorn, pero seguirás limitado por el número de workers disponibles. Una alternativa más efectiva es utilizar gevent, que permite que las solicitudes se manejen de forma asíncrona.

Para utilizar gevent, ejecuta el siguiente comando:

gunicorn -b localhost:8080  --worker-class=gevent mysite:app

Ahora podrás ejecutar tantas solicitudes lentas como desees, y la solicitud rápida se completará rápidamente. Sin embargo, es importante tener en cuenta que el uso de gevent solo es efectivo para las solicitudes que hacen que el worker espere datos. Si una solicitud es lenta debido a un gran número de cálculos internos en lugar de esperar datos externos, gevent no mejorará el rendimiento de manera significativa.

En resumen, al utilizar gevent en gunicorn, puedes mejorar el rendimiento de tu aplicación cuando el worker está esperando datos de una API externa.

Esto permite que las solicitudes se manejen de forma asíncrona, evitando bloqueos y acelerando el tiempo de respuesta.