Utiliser l'application_name de PostgreSQL avec Django


L'utilisation d'un ORM efficace comme celui de Django abstrait la base de données au point de rendre parfois le debug malaisé voire difficile. Il n'est pas souvent évident de remonter jusqu'à la vue qui a généré une requête SQL consommatrice de ressources qui aurait été détectée dans les logs d'un serveur de production. Nous avons déjà pour cela mis en oeuvre chez PeopleDoc une app Django nommée django-sql-log. Dans la suite logique de cette démarche nous allons voir dans ce billet comment enrichir la chaîne de connexion pour ajouter de l'information dans les logs du serveur PostgreSQL.

La libpq de PostgreSQL inclut un paramètre optionnel nommé application_name constitué d'une chaine de caractères laissée à la disposition des développeurs d'application. Il faut entendre ici le mot application dans son sens générique et non pas dans un contexte Django. Cette information, si présente, est consultable dans les logs (au moyen du format %a du paramètre de log_line_prefix), mais également dans des vues systèmes comme pg_stat_activity.

L'utilisation de cette option depuis Django se fait au moyen de l'entrée OPTIONS du dict DATABASES dans les settings comme suit.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'HOST': 'localhost',
        'PORT': 5432,
        'NAME': 'foo',
        'USER': 'rodo',
        'PASSWORD': 'neverusetoto',
        'OPTIONS': {'application_name': 'foo'}
    }
},

Le dict OPTIONS est passé tel quel à la bibliothèque psycopg2, mais n'inclut pas encore à ce jour toutes les options possibles de la libpq.

Utilisé de cette manière, tout le code utilisant ces settings signera ses actions dans les logs avec le nom foo.

L'intérêt devient plus évident si vous utilisez un application_name différent pour certaines tâches spécifiques au sein de votre code comme dans une commande de management, vous pouvez en effet surcharger directement les settings dans une commande comme suit :

from django.conf import settings
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'A real big command'

    def handle(self, *args, **options):
        # toujours le faire au plus tôt avant d'utiliser une QuerySet
        settings.DATABASES['default']['OPTIONS'] = {'application_name': 'expire_all'}
        dothejob()

Ainsi si vous paramétrez vos logs PostgreSQL avec un log_line_prefix de ce type :

djangofoo=# select current_setting('log_line_prefix');
-[ RECORD 1 ]---+----------------------------------------------
current_setting | %t [%p]: [%l-1] user=%u,db=%d,host=%h,app=%a

vous obtiendrez dans les logs les lignes suivantes ; où il devient de suite plus aisé de retrouver l'origine d'un COUNT désastreux.

2015-06-22 20:18:14 CEST [4761]: [14-1]
user=rodo,db=djangofoo,host=::1,app=expire_all LOG:  duration: 7.471 ms
statement: SELECT COUNT(*) FROM "hotel_hotel WHERE town_id=23"
2015-06-22 20:18:21 CEST [4601]: [7-1] user=rodo,db=djangofoo,host=::1,app=[unknown] LOG:  duration: 377.968 ms
statement: SELECT COUNT(*) FROM "hotel_hotel"
2015-06-22 20:19:03 CEST [4809]: [1-1] user=rodo,db=djangofoo,host=[local],app=psql LOG:  duration: 9.583 ms
statement: SELECT COUNT(*) FROM hotel_hotel WHERE closed=true;

Dans ce cas, la première commande a été éxécutée dans la commande manage que l'on a nommée expire_all, la deuxième dans un shell Django (oui il serait judicieux que cela soit plus explicite) et enfin la dernière directement avec le client psql. Sans cette information les 3 commandes ayant été exécutées depuis la même machine avec le même user il aurait été impossible de retrouver nos petits.

Il ne faut pas que cette solution vous dévie toutefois de la meilleure séparation possible en créant des utilisateurs spécifiques suivants les contextes, qui permet non seulement d'identifier la source mais aussi de borner le périmètre d'action afin de se protéger d'éventuelles erreurs applicatives.

More articles

Stockage Array avec Django


Actuellement dans un de nos projets nous stockons un ensemble d'id provenant d'une table dans une autre base de données, ces valeurs étant coûteuses à calculer et variant peu nous les stockons directement au niveau du tuple dans une colonne de type text en séparant chaque valeur par une virgule …

Supprimer un grand nombre d'objets dans une base


Dans ce billet nous allons nous intéresser aux différentes méthodes utilisables pour supprimer un grand nombre d'objets dans une base de données PostgreSQL avec Django.

Le test est réalisé sur des tables contenant 100 000 lignes et la suppression représente environ 16% des lignes. Les différentes mesures sont réalisées sur …

Aller à l'essentiel avec only et defer


Après avoir étudié l'intérêt de la pagination par clé dans un récent article nous allons nous pencher cette fois sur les queryset et en particulier sur la méthode only()

Le bench est réalisé sur une table de 10 000 lignes, ce qui est peu mais déjà suffisant pour mettre en …

Paginator c'est bien, ... pour paginer


Après la lecture de l'excellent article de Markus Winnand intitulé no-offset dans lequel il décrit la pagination par clé j'ai réalisé un test pour mesurer l'impact de performance sur un cas réel. Django propose de base la classe Paginator pour faire de la pagination, l'implémentation de celle-ci utilise OFFSET lors …

1 / 1