J'utilise Django avec Apache et mod_wsgi et PostgreSQL (tous sur le même hôte), et j'ai besoin de gérer beaucoup de demandes de pages dynamiques simples (des centaines par seconde). J'ai rencontré un problème qui le goulot d'étranglement est qu'un Django n'a pas de connexion de base de données persistante et se reconnecte à chaque demande (cela prend près de 5 ms). En faisant un benchmark, j'ai obtenu qu'avec une connexion persistante je peux gérer près de 500 r/s tandis que sans je reçois seulement 50 r/s.
Quelqu'un a-t-il des conseils? Comment modifier Django pour utiliser une connexion persistante? Ou accélérer la connexion de python vers DB
Merci d'avance.
Django 1.6 a ajouté prise en charge des connexions persistantes (lien vers la doc pour Django 1.9) :
Les connexions persistantes évitent la surcharge de rétablir une connexion à la base de données dans chaque demande. Ils sont contrôlés par le paramètre CONN_MAX_AGE qui définit la durée de vie maximale d'une connexion. Il peut être défini indépendamment pour chaque base de données.
Dans Django trunk, éditez Django/db/__init__.py
et commentez la ligne:
signals.request_finished.connect(close_connection)
Ce gestionnaire de signal le fait se déconnecter de la base de données après chaque demande. Je ne sais pas quels seront tous les effets secondaires de cette opération, mais cela n'a aucun sens de démarrer une nouvelle connexion après chaque demande; cela détruit les performances, comme vous l'avez remarqué.
J'utilise cela maintenant, mais je n'ai pas fait un ensemble complet de tests pour voir si quelque chose se casse.
Je ne sais pas pourquoi tout le monde pense que cela a besoin d'un nouveau backend ou d'un pool de connexion spécial ou d'autres solutions complexes. Cela semble très simple, bien que je ne doute pas qu'il y ait quelques pièges obscurs qui les ont incités à le faire en premier lieu - qui devraient être traités avec plus de sensibilité; 5 ms de frais généraux pour chaque demande, c'est beaucoup pour un service haute performance, comme vous l'avez remarqué. (Cela me prend 150 ms - Je n'ai pas encore compris pourquoi.)
Edit: un autre changement nécessaire est dans Django/middleware/transaction.py; supprimez les deux tests transaction.is_dirty () et appelez toujours commit () ou rollback (). Sinon, il ne commettra pas de transaction s'il ne lit que dans la base de données, ce qui laissera les verrous ouverts qui devraient être fermés.
J'ai créé un petit patch Django qui implémente le pool de connexion de MySQL et PostgreSQL via le pool sqlalchemy.
Cela fonctionne parfaitement sur la production de http://grandcapital.net/ pendant une longue période de temps.
Le patch a été écrit après avoir googlé un peu le sujet.
Avertissement: je n'ai pas essayé cela.
Je crois que vous devez implémenter un back-end de base de données personnalisé. Il existe quelques exemples sur le Web qui montrent comment implémenter un serveur principal de base de données avec un pool de connexions.
L'utilisation d'un pool de connexions serait probablement une bonne solution dans votre cas, car les connexions réseau sont maintenues ouvertes lorsque les connexions sont retournées au pool.
Les deux publications utilisent MySQL - vous pouvez peut-être utiliser des techniques similaires avec Postgresql.
Modifier:
J'ai créé un petit backend psycopg2 personnalisé qui implémente une connexion persistante à l'aide d'une variable globale. Avec cela, j'ai pu améliorer le nombre de demandes par seconde de 350 à 1600 (sur une page très simple avec quelques sélections) Il suffit de l'enregistrer dans le fichier appelé base.py
dans n'importe quel répertoire (par exemple postgresql_psycopg2_persistent) et défini dans les paramètres
DATABASE_ENGINE à projectname.postgresql_psycopg2_persistent
NOTE !!! le code n'est pas threadsafe - vous ne pouvez pas l'utiliser avec python threads en raison de résultats inattendus, en cas de mod_wsgi veuillez utiliser le mode démon prefork avec threads = 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
Ou voici un thread safe, mais python n'utilisent pas plusieurs cœurs, vous n'obtiendrez donc pas une telle amélioration des performances qu'avec le précédent. Vous pouvez utiliser celui-ci avec un processus multiple aussi.
# 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