es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cerrando conexiones de Django ORM en un entorno multi-threaded.

Tengo el siguiente código en un script independiente que utiliza el ORM de Django (fuera de Django) con multidifusión.

import threading

MAX_THREADS = 30
semaphore = threading.Semaphore(value=MAX_THREADS)

books = Books.objects.all()
for book in books:
    book_id = book.id
    t = threading.Thread(target=process_book, args=[book_id])
    t.start()
    threads.append(t)

for t in threads:
    t.join()

def process_book(book_id):
    semaphore.acquire()
    book = Books.objects.get(id=book_id)
    # Hacer cosas que toman tiempo aquí
    book.save()
    semaphore.release()

Una vez que el número de hilos alcanza la configuración MAX_CLIENT_CONN de postgres (que es 100 de forma predeterminada), comienzo a recibir el siguiente error con llamadas posteriores:

operationalError at FATAL: remaining connection slots are reserved for
non-replication superuser connections

Investigando esto, llegué a soluciones que implican el uso de una herramienta de agrupamiento de bases de datos como pgbouncer. Sin embargo, esto solo detiene las nuevas conexiones hasta que haya conexiones disponibles, pero nuevamente después del tiempo de espera de consulta de Django, me encuentro con lo siguiente:

OperationalError at / query_wait_timeout server closed the
connection unexpectedly This probably means the server terminated
abnormally before or while processing the request.

Entiendo que esto está sucediendo porque los hilos no están cerrando las conexiones de la base de datos que están realizando, pero no estoy seguro de cómo cerrar las conexiones de llamada al ORM. ¿Hay algo que pueda hacer de manera diferente en el flujo de código anterior para reducir el número de conexiones?

Necesito realizar una consulta individual en cada instancia para poder actualizarlos, ya que .update() o .save() no funcionarán en elementos de la consulta (queryset).

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    El problema que estás enfrentando se debe a la cantidad de conexiones creadas por tu script. Una vez alcanzado el número máximo de conexiones permitidas por PostgreSQL, se rechazan cualquier nueva solicitud de conexión, lo que resulta en el error que estás viendo.

    Una solución para este problema es utilizar un administrador de conexiones como pgbouncer para gestionar las solicitudes de conexión. Sin embargo, esto solo retrasará el problema hasta que las conexiones estén disponibles. Además, aún te enfrentas al problema de los tiempos de espera debido a que las conexiones están abiertas durante demasiado tiempo.

    Para solucionar este problema, debes modificar tu código para asegurarte de que las conexiones se cierren después de que hayan sido utilizadas. La forma más fácil de hacer esto es utilizar la declaración with al consultar la base de datos de la siguiente manera:

    with connection.cursor() as cursor:
        cursor.execute('SELECT * FROM myapp_mymodel WHERE id = %s', [model_id])
        row = cursor.fetchone()
    

    En tu código, puedes modificarlo de la siguiente manera:

    import threading 
    from django.db import connection
    
    MAX_THREADS = 30
    semaphore = threading.Semaphore(value=MAX_THREADS)
    
    books = Books.objects.all()
    for book in books:
        book_id = book.id
        t = threading.Thread(target=process_book, args=[book_id])
        t.start()
        threads.append(t)
    
    for t in threads:
        t.join()
    
    def process_book(book_id):
        semaphore.acquire()
        with connection.cursor() as cursor:
            cursor.execute('SELECT * FROM myapp_mymodel WHERE id = %s', [book_id])
            row = cursor.fetchone()
            # Hacer algunas cosas que llevan tiempo aquí
            cursor.execute('UPDATE myapp_mymodel SET myfield = %s WHERE id = %s', [newvalue, book_id])
        semaphore.release()
    

    Al utilizar la declaración with, la conexión se cerrará automáticamente después de que se haya ejecutado la consulta, lo que reduce el número de conexiones en uso.

    Además, debes considerar la posibilidad de utilizar el agrupamiento de conexiones incorporado en Django, que proporciona una forma de limitar el número de conexiones y asegurarse de que las conexiones inactivas se cierren después de cierto tiempo. Puedes configurarlo en la sección DATABASES de tu archivo de configuración de Django.

Comments are closed.