conexión de base de datos persistente Django
-
13-09-2019 - |
Pregunta
Estoy usando Django con Apache y mod_wsgi y PostgreSQL (todo el mismo host), y tengo que manejar una gran cantidad de simples solicitudes de páginas dinámicas (cientos por segundo). Me encontré con un problema que el cuello de botella es que un django no tienen conexión a la base de datos persistente y vuelve a conectar en cada uno de peticiones (que lleva cerca de 5 ms). Mientras que hace un punto de referencia que llegué con conexión persistente que puede manejar cerca de 500 r / s, mientras que sin consigo sólo el 50 r / s.
Alguien tiene algún consejo? Cómo modificar Django para utilizar la conexión persistente? O acelerar la conexión de pitón a DB
Gracias de antemano.
Solución
Django 1.6 ha añadido conexiones persistentes apoyo (enlace a doc para django 1.9) :
Las conexiones persistentes evitar la sobrecarga de re-establecimiento de 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 ajustar independientemente para cada base de datos.
Otros consejos
Trate PgBouncer - pooler una conexión ligera para PostgreSQL. Características:
- Varios niveles de brutalidad cuando rotativos conexiones:
- agrupación de sesiones
- Transacción puesta en común
- Declaración de la puesta en común
requisitos - Baja memoria (2k por conexión por defecto).
En trunk de Django, editar y django/db/__init__.py
comentario la línea:
signals.request_finished.connect(close_connection)
Este manejador de la señal hace que se desconecte de la base de datos después de cada petición. No sé lo que todos los efectos secundarios de hacer esto será, pero que no tiene ningún sentido para iniciar una nueva conexión después de cada petición; destruye el rendimiento, como se ha notado.
Estoy usando esto ahora, pero no te has hecho un conjunto completo de pruebas para ver si se rompe nada.
No sé qué todo el mundo piensa que esto necesita un nuevo backend o una conexión especial pooler u otras soluciones complejas. Esto parece muy simple, aunque no cabe duda de que hay algunas trampas oscuras que les hizo hacer esto en primer lugar - que debe tratarse con más sensatez; 5ms rendimiento para cada petición es bastante para un servicio de alto rendimiento, como se ha notado. (Me toma 150 ms -. Yo no te has descubierto por qué aún)
Edit: otro cambio necesario es en django / middleware / transaction.py; eliminar las pruebas de dos transaction.is_dirty () y llamar siempre a cometer () o rollback (). De lo contrario, no va a confirmar una transacción si sólo se lee de la base de datos, lo que dejará abrir cerraduras que debe ser cerrado.
I creado un pequeño Django parche que implementa la agrupación de conexiones de MySQL y PostgreSQL a través de la agrupación de sqlalchemy.
Esto funciona perfectamente en la producción de http://grandcapital.net/ durante un largo periodo de tiempo.
El parche fue escrito después de googlear un poco el tema.
exención de responsabilidad:. No he probado este
Creo que es necesario implementar un back-end base de datos personalizada. Hay algunos ejemplos en la web que muestra cómo implementar una base de datos back-end con la agrupación de conexiones.
El uso de un conjunto de conexiones, probablemente sería una buena solución para usted caso, ya que las conexiones de red se mantienen abiertas cuando las conexiones son devueltos a la piscina.
- Este post logra el esto mediante parches Django (uno de los comentarios señala que es mejor aplicar una costumbre posterior terminar en el exterior del código del núcleo de Django)
- Este post es una implementación del extremo posterior db una costumbre
Tanto los mensajes utilizan MySQL - tal vez usted es capaz de utilizar técnicas similares con PostgreSQL
.Editar
he hecho alguna pequeña backend psycopg2 personalizado que implementa conexión persistente mediante la variable global.
Con este pude mejorar la cantidad que de solicitudes por segundo de 350 a 1600 (en la página muy simple, con pocas selecciona)
Sólo tiene que guardar en el archivo llamado base.py
en cualquier directorio (por ejemplo postgresql_psycopg2_persistent) y ajuste en la configuración
DATABASE_ENGINE a projectname.postgresql_psycopg2_persistent
NOTA !!! el código no es multi-hilo - no se puede utilizar con hilos de pitón a causa de los resultados unexpectable, en caso de mod_wsgi por favor utilice modo demonio prefork con hilos = 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í es una caja fuerte hilo, pero las discusiones pitón no utilizan múltiples núcleos, por lo que no conseguirá tales como aumento de rendimiento con el anterior. Puede utilizar éste con un solo proceso de múltiples 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