web-dev-qa-db-fra.com

Django connexion de base de données persistante

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.

54
HardQuestions

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.

28
Cesar Canassa

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.

20
Glenn Maynard

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.

15
Igor Katson

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.

  • Cet article accomplit cela en corrigeant Django (l'un des commentaires souligne qu'il est préférable d'implémenter un back-end personnalisé en dehors du noyau Django code)
  • Cet article est une implémentation d'un back-end db personnalisé

Les deux publications utilisent MySQL - vous pouvez peut-être utiliser des techniques similaires avec Postgresql.

Modifier:

  • Le livre Django mentionne le regroupement de connexions Postgresql, en utilisant pgpool ( tutoriel ).
  • Quelqu'un a posté n correctif pour le backend psycopg2 qui implémente le pool de connexions. Je suggère de créer une copie du back-end existant dans votre propre projet et de le patcher.
3
codeape

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
0
HardQuestions