Conexión de base de datos persistente Django.

Estoy usando django con apache y mod_wsgi y PostgreSQL (todos en el mismo host), y necesito manejar muchas solicitudes de páginas dinámicas simples (cientos por segundo). Me enfrenté con el problema de que el cuello de botella es que un django no tiene una conexión de base de datos persistente y se vuelve a conectar en cada solicitud (que lleva cerca de 5 ms). Al hacer un punto de referencia, conseguí que con una conexión persistente puedo manejar cerca de 500 r / s, mientras que sin obtener solo 50 r / s.

¿Alguien tiene algún consejo? ¿Cómo modificar django para usar conexión persistente? O acelerar la conexión de python a DB

Gracias por adelantado.

Django 1.6 ha agregado soporte de conexiones persistentes (enlace a doc para django 1.9) :

Las conexiones persistentes evitan la sobrecarga de restablecer una conexión a la base de datos en cada solicitud. Están controlados por el parámetro CONN_MAX_AGE que define la vida útil máxima de una conexión. Se puede configurar de forma independiente para cada base de datos.

Pruebe PgBouncer , un agrupador de conexiones ligero para PostgreSQL. caracteristicas:

  • Varios niveles de brutalidad al rotar conexiones:
    • Agrupación de sesiones
    • Agrupación de transacciones
    • Agrupación de estados de cuenta
  • Bajo requerimiento de memoria (2k por conexión por defecto).

En el troncal de Django, edite django/db/__init__.py y comente en la línea:

 signals.request_finished.connect(close_connection) 

Este controlador de señales hace que se desconecte de la base de datos después de cada solicitud. No sé cuáles serán todos los efectos secundarios de hacer esto, pero no tiene ningún sentido iniciar una nueva conexión después de cada solicitud; Destruye el rendimiento, como habrás notado.

Estoy usando esto ahora, pero no he hecho un conjunto completo de pruebas para ver si algo se rompe.

No sé por qué todo el mundo piensa que esto necesita un nuevo backend o un pooler de conexión especial u otras soluciones complejas. Esto parece muy simple, aunque no dudo que haya algunos errores oscuros que los obligaron a hacer esto en primer lugar, lo que debería tratarse con más sensatez; La sobrecarga de 5 ms por cada solicitud es bastante para un servicio de alto rendimiento, como se ha dado cuenta. (Me toma 150 ms. Aún no he descubierto por qué).

Edición: otro cambio necesario está en django / middleware / transaction.py; elimine las dos pruebas transaction.is_dirty () y siempre llame a commit () o rollback (). De lo contrario, no se confirmará una transacción si solo se lee de la base de datos, lo que dejará los lockings abiertos que deberían cerrarse.

Creé un pequeño parche de Django que implementa la agrupación de conexiones de MySQL y PostgreSQL a través de la agrupación sqlalchemy.

Esto funciona perfectamente en la producción de http://grandcapital.net/ durante un largo período de tiempo.

El parche fue escrito después de buscar en Google el tema un poco.

Descargo de responsabilidad: no he intentado esto.

Creo que necesitas implementar un back-end de base de datos personalizado. Hay algunos ejemplos en la web que muestran cómo implementar un back-end de base de datos con la agrupación de conexiones.

El uso de un grupo de conexiones probablemente sea una buena solución para su caso, ya que las conexiones de red se mantienen abiertas cuando las conexiones se devuelven al grupo.

  • Esta publicación logra esto parchando Django (uno de los comentarios señala que es mejor implementar un back end personalizado fuera del código django del núcleo)
  • Esta publicación es una implementación de un back-end db personalizado.

Ambas publicaciones usan MySQL; quizás puedas usar técnicas similares con Postgresql.

Editar:

  • El libro Django menciona la agrupación de conexiones Postgresql, usando pgpool ( tutorial ).
  • Alguien publicó un parche para el servidor de fondo psycopg2 que implementa la agrupación de conexiones. Sugiero crear una copia del back-end existente en su propio proyecto y parchearlo.

Hice un pequeño backend psycopg2 personalizado que implementa una conexión persistente utilizando una variable global. Con esto pude mejorar la cantidad de solicitudes por segundo de 350 a 1600 (en una página muy simple con pocas selecciones) Simplemente guárdelo en el archivo llamado base.py en cualquier directorio (por ejemplo, postgresql_psycopg2_persistent) y establezca la configuración

DATABASE_ENGINE a projectname.postgresql_psycopg2_persistent

¡¡¡NOTA!!! el código no es seguro para subprocesos; no puede usarlo con subprocesos de Python debido a resultados inesperados; en el caso de mod_wsgi, use el modo de demonio prefork con subprocesos = 1


 # Custom DB backend postgresql_psycopg2 based # implements persistent database connection using global variable from django.db.backends.postgresql_psycopg2.base import DatabaseError, DatabaseWrapper as BaseDatabaseWrapper, \ IntegrityError from psycopg2 import OperationalError connection = None class DatabaseWrapper(BaseDatabaseWrapper): def _cursor(self, *args, **kwargs): global connection if connection is not None and self.connection is None: try: # Check if connection is alive connection.cursor().execute('SELECT 1') except OperationalError: # The connection is not working, need reconnect connection = None else: self.connection = connection cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs) if connection is None and self.connection is not None: connection = self.connection return cursor def close(self): if self.connection is not None: self.connection.commit() self.connection = None 

O aquí hay uno seguro para los subprocesos, pero los subprocesos de Python no utilizan varios núcleos, por lo que no obtendrás ese aumento de rendimiento como con el anterior. Puedes usar este con multiproceso uno también.

 # Custom DB backend postgresql_psycopg2 based # implements persistent database connection using thread local storage from threading import local from django.db.backends.postgresql_psycopg2.base import DatabaseError, \ DatabaseWrapper as BaseDatabaseWrapper, IntegrityError from psycopg2 import OperationalError threadlocal = local() class DatabaseWrapper(BaseDatabaseWrapper): def _cursor(self, *args, **kwargs): if hasattr(threadlocal, 'connection') and threadlocal.connection is \ not None and self.connection is None: try: # Check if connection is alive threadlocal.connection.cursor().execute('SELECT 1') except OperationalError: # The connection is not working, need reconnect threadlocal.connection = None else: self.connection = threadlocal.connection cursor = super(DatabaseWrapper, self)._cursor(*args, **kwargs) if (not hasattr(threadlocal, 'connection') or threadlocal.connection \ is None) and self.connection is not None: threadlocal.connection = self.connection return cursor def close(self): if self.connection is not None: self.connection.commit() self.connection = None