S'abonner au Flux RSS

lundi, août 10 2015

Utiliser pg_shard avec Django

L'hiver dernier CitusData à ouvert le code source de son outil de partitionnement pg_shard, le code est désormais publié sous licence LGPL version 3, et disponible sur github. Le 30 juillet dernier la version 1.2 a été releasé, ce fut l'occasion pour moi de tester la compatibilité de Django avec cette nouvelle extension PostgreSQL.

Pour rappel le sharding permet de distribuer le contenu d'une table sur plusieurs serveurs, pg_shard permet également de gérer de multiples copies d'un même réplicats afin de palier à une éventulle faille sur l'un des noeuds. L'intérêt principal du sharding est de pouvoir garantir la scalabilité quand le volume de données augmente rapidement, l'accés aux données se faisant toujours sur le noeud principal sans avoir à prendre en compte les noeuds secondaires qui sont trasparents pour le client.

Autant le dire tout de suite, et ne pas laisser le suspens s'installer, Django n'est pas compatible avec pg_shard, cela pour trois raisons principales détaillée ci-dessous. D'auutres points sont peut-être bloquant, mais je n'ai pas introspecté plus en avant après avoir déjà constaté ces premiers points de blocage.

Lors de la sauvegarde d'un nouvel objet dans la base Django utilise la clause RETURNING dans l'INSERT afin de récupérer l'id de l'objet. A ce jour pg_shard ne supporte pas RETURNING, un ticket est en cours, espérons qu'une future version soit publiée avec cette fonctionnalité.

Plus problématique car cela demanderai un hack un peu plus profond dans l'ORM de Django, le non support des séquences qui sont utilisées par le type SERIAL afin de bénéficier de la numérotation automatique et unique des clés primaires. C'est ce type qui est utilisé par défaut par Django pour les pk. Là encore des discussions sont en cours pour supporter les sequences dans pg_shard.

Enfin et c'est peut-être ce qui serait le plus bloquant pour une utilisation avec Django ou un autre ORM, pg_shard ne supporte pas les transactions multi-requêtes. Les transactions étant la base de la garantie de l'intégrité des données ; à part être dans un cas d'usage où l'on ne modifie pas plus d'une donnée à la fois, cela peut être une raison pour ne pas adopter pg_shard dans l'état.

Malgré ces constats pg_shard reste une solution très intéressante, qu'il faut garder dans un coin de sa veille techno, à l'époque où le big data revient si souvent dans les conversations autour de la machine à café.

mercredi, août 5 2015

pgBouncer dans un contexte Django

PgBouncer est un gestionnaire de pool de connexion pour PostgreSQL très efficace, il permet de réduire drastiquement le temps de connexion à la base depuis votre client.

Dans un contexte d'utilisation avec Django l'intérêt peut ne pas apparaître de suite, le temps passé dans l'exécution et la récupération de la requête étant souvent bien supérieur au temps de connexion. Ce paradigme tend à s'inverser dans un contexte d'API ; j'ai eu récemment l'occasion de mesurer l'impact de son utilisation sur un cas réel suite à un problème de timeout sur une API.

L'API est consommée à des taux certes raisonnables, autour de 25 appels par secondes, mais l'accroissement régulier faisait apparaitre des TIMEOUT de plus en plus souvent au niveau du client. En frontal les appels sont reçus par Nginx qui renvoit ceux-ci à des process gunicorn, le timeout coté Nginx est de 60 secondes, c'est ce timeout qui se déclenche. Les mesures sur l'infra de tests de performances continus montraient des temps de réponses de l'ordre de 120msec sous faible charge, ce qui n'était pas cohérent avec les 60 sec du timeout de Nginx.

Seulement après une revue complète de l'infrastucture du SI il est apparu que sur l'environnement de test pgbouncer était installé et correctement configuré, alors que cela n'était le cas du coté de la production. J'ai alors mené une série de tests avec et sans pgbouncer sur la même architecture, afin de mesurer son impacte réel ; PgBouncer faisant partie des préconisations initiales que j'avais faite sur ce projet.

Le test effectue un appel simple avec des données aléatoire et injecte un nombre croissant d'utilisateur pour arriver au plateau de 60 users/sec; il a été mené avec Gatling.

Les premiers tests avec pgbouncer donnent des temps de réponses médians de 285ms avec un 99th percentile à 1650ms, toutes les requêtes sont traitées avec succès

with-pgbouncer.png

Si on débranche pgbouncer le temps de réponses médian croit à 14487ms et surtout le max dépasse 60126ms ce qui donne un nombre croissant de requête en timeout sur la fin du test quand on arrive à pleine charge.

without-pgbouncer.png

Sur la plateforme de test PgBouncer est installé sur la machine qui fait tourner les process gunicorn, le configuration de Django est donc positionnée sur le loopback. La base de données PostgreSQL est elle sur une machine distante avec une connexion sur le LAN.

PgBouncer peut apparaître comme un outil compliqué quand on a pas l'habitude des bases de données, mais il est fort à parier que votre DBA le connait déjà, alors si l'utilisation de vos API croît ayez le réflex PgBouncer !

dimanche, mars 22 2015

De retour de Confoo

J'ai eu la chance cette année de donner deux conférences à ConFoo qui c'est déroulé mi-frévier à Montréal, je m'étais promis dans l'avion de rédiger un billet sur cette expérience, et je trouve enfin le temps de le faire près d'un mois et demi après. Anyway, il est toujours temps de dire que j'ai été assez bluffé par l'organisation parfaite de cet évènement, en toute honnêteté je ne vois rien à redire ; il y a toujours des hics et des couacs dans tout évènement, ici même en cherchant bien impossible de citer un problème ou un dysfonctionnement. Quand on a connu des conférences où l'on devait venir avec sa propre bouteille d'eau et où les organisateurs n'avaient pas dénier offrir un café, ça change ! Et quand on vient présenter un sujet c'est vraiment très appéciable de sentir que tout roule et que l'on ne sera pas obligé de courir au troisième sous-sol pour trouver un projecteur qui fonctionne. J'ai été particulièrement impressionné par l'équipe de bénévole d'une redoutable efficacité, reçevoir dans les 30 minutes les fiches retour d'avis des participants dans sa boîte mail, cela frôle la perfection, chapeaux les artistes !

Bien sûr présentant des sujets peut-être un peu borderline je n'ai pas eu à souffrir d'une affluence à faire fermer les portes en avances. On sent un poids historique des conférences tout PHP des premières années, néanmoins l'ouverture est là et à encourager et je suis persuadé que le temps donnera raison aux organisateurs de s'ouvrir aux autres technologies.

Pour ma part ma première présentation sur Django et PostgreSQL sour la charge a certes reçu un accueil limité, mais les petites salles ont l'avantage de permettre de vrais échanges et cela a été un plaisir pour moi de faire découvrir quelques astuces de Django aux participants.

J'avais remanié ma désormais classique conférence sur Tsung de 1 à 1 million qui s'est déroulée avec un public plus fournit que la veille, les échanges post présentation ayant dûs se prolonger à l'extérieur.

Une vraie belle expérience que je vous invite à vivre tous !

lundi, septembre 22 2014

Django Paginator oui mais

Après la lecture de l'excellent article de Markus Winnand no-offset j'ai réalisé un test pour mesurer la différence de performance de l'utilisation d'OFFSET avec Django.

La suite à lire sur le blog Novapost's paradize.

mercredi, avril 3 2013

Créer un lot de User dans Django

Lors d'un récent test de charge sur une application Django j'ai eu à créer 10K utilisateurs pour tester la charge sur le processus de login. Ce besoin de création d'utilisateur de test étant récurrent j'ai écrit une commande manage (1) afin de créer facilement un grand nombre d'utilisateurs mais également de conserver leur id pour suppression ultérieure. La liste des ids est affichée sur stdout pour faire au plus simple.

Par défaut on crée 10 utilisateurs et on spécifie comme premier argument le nombre total souhaité.

Le code de la classe, simple et efficace :

"""                                                                                                       
Create users in batch                                                                                     
"""
import json
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User

class Command(BaseCommand):
    help = 'Create test accounts'

    def handle(self, *args, **options):
        nb_accounts = 10
        mac = 0
        userids = []

        if len(args) > 0:
            nb_accounts = int(args[0])

        while mac < nb_accounts:
            username = 'tsung-%04d' % (mac)
            email = 'tsung-%04d@example.com' % (mac)
            password = 'password-%04d' % (mac)
            user = User.objects.create(username=username,
                                       email=email)
            user.set_password(password)
            user.save()
            userids.append(user.id)
            mac += 1

        print json.dumps(userids)

vendredi, mars 29 2013

Logger les requêtes SQL d'un site Django en production

Si il est intéressant d'utiliser un ORM pour manipuler des objets dans une base de données relationnelle il présente également inconvénient de cacher les requêtes réellement exécutées sur la base de données. Tous les frameworks de développement n'intègre pas forcément de moyen simple d'avoir accés aux requêtes, Django permet de faire cela à partir d'extension comme django-devserver (1[https://github.com/dcramer/django...) ou d'autres ; extensions qui sont utilisables en développement ou sur des machines de pre-prod, mais malheureusement pas en production. Disons que l'on ne s'intéressera pas ici aux sites qui utilisent runserver sur leur serveur de prod, humm.

Résumons le contexte on a une application Django en production et on a besoin de récupérer l'intégralité des commandes SQL passées lors d'une opération de bench.

On va pouvoir faire cela simplement car Django intégre de base un (logger) nommé django.db qui va nous permettre de le faire. On va logger toutes les requêtes directement dans un fichier en configurant dans les settings un handler sur un fichier et en utilisant le logger djangodb.

En pratique on ajoute une entrée file dans la section handlers

    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/tmp/django-sql.log',
        },
    },

et une entrée django.db dans la section loggers qui utilise l'entrée file

    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'django.db': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
            }
    }

On aura toutes nos requêtes sql dans le fichier /tmp/django-sql.log sous la forme

(0.000) SELECT "graph_dashitem"."id", "graph_dashitem"."dash_id", "graph_dashitem"."graph_id", "graph_dashitem"."order" FROM "graph_dashitem" WHERE "graph_dashitem"."dash_id" = 2 ORDER BY "graph_dashitem"."order" ASC; args=(2,)

Le premier champs contient le temps d'exécution et le dernier la liste d'arguments, on obtient un fichier facilement parsable dans lequel on pourra rechercher les requêtes gourmandes en temps par exemple. La prochaine étape sera de passer les requêtes dans EXPLAIN pour les analyser.

Avertissement si votre site a un fort traffic le log de toutes les requêtes va générer beaucoup de consommation d'IO, et d'espace disque, pensez à désactiver les logs quand vous avez finit votre bench.

mercredi, février 6 2013

PyDeb from PyPI to .deb

Dans mon précédent billet j'évoquais les paquets debian que j'avais réalisé pour Django au sein du projet Pyrede. Cette activité de packaging ayant pris de l'ampleur les paquets seront désormais disponibles au sein du Projet Pydeb.

Le projet PyDEB se compose d'un dépôt de paquets et d'un blog sur lequel je publie les nouveaux paquets ainsi que leurs mises à jour.

Les derniers paquets réalisés sont redis, hamlpy, vectorformats, ...

Le projet Pyrede d'analyse de fichier requirements.txt continue son activité avec PyDEB comme ressource bien évidemment.

mardi, janvier 29 2013

Paquets Debian pour Django

Si j'aime les virtualenv quand je code je reste attachés aux paquets pour le déploiement et j'essaye tant que faire se peut de résister aux 'pip install -r requirements.txt' sur mes machines de production. C'est dans ce contexte que j'ai packagé quelques paquets relatifs à Django que je vais essayer de maintenir au sein du projet Pyrede.

http://pyrede.quiedeville.org/debian/

Le fichier repo.key contient la signature utilisée pour signer les paquets.

jeudi, décembre 20 2012

De whoosh à Solr avec Django Haystack

Sur un déploiement Django avec une indexation réalisée avec Haystack je me suis retrouvé face à un problème bloquant. L'application doit permettre à différents users unix de la machine d'insérer des données au moyen d'une commande de management de Django ; les objets insérés dans la base de données sont indéxés en temps réels avec un RealTimeSearchIndex. Le peu de volume de données avait orienté le choix du backend d'indexation vers whoosh, whoosh travaillant avec des fichiers locaux l'appel de la commande manage par différents users unix a engendré des problèmes de permissions sur les fichiers d'index. Une première solution de contournement a été d'utiliser la commande sudo mais cela n'est pas satisfaisant sur le long terme sans une formation des utilisateurs à sudo, pour contourner ce problème je me suis orienté vers l'utilisation d'un backend Solr. Je vais décrire ici la mise en oeuvre de cette solution sur une Debian Wheezy car cela ne s'est pas fait sans problèmes.

Installation de Solr

Le choix fait a été d'utiliser Solr avec le server d'application Jetty, l'installation sous Debian est toujours aussi simple qu'apt-get dans notre cas de figure ici il est nécessaire d'installer les paquets

  • jetty
  • solr-jetty

Le serveur d'indexation étant sur une machine séparée de l'instance Django il faut également installer le package python-pysolr sur le serveur qui fait tourner l'application Django elle même.

Il existe un bug dans le paquet solr-jetty, l'installation créé un lien symbolique cassé.

/var/lib/jetty/webapps/solr doit pointer vers /usr/share/solr/web et non vers /usr/share/solr/webapp comme le fait l'installation du package

Schema généré non conforme

Une fois le serveur installé il faut configurer Solr en conformité avec les données de l'application Django, pour cela on génère un fichier nommé schema.xml avec la commande :

manage.py build_solr_schema > schema.xml

Une fois ce fichier généré copiez le sur le serveur Solr dans /etc/solr/conf/schema.xml

J'utilise une version 2.0.0 de Haystack et il est nécessaire de modifier quelque peu le fichier généré pour le rendre compatible avec Solr 3.6.0 présent à l'heure d'écriture de ce billet dans Wheezy.

Tout d'abord il faut remplacer la chaine stopwords_en.txt par lang/stopwords_en.txt pour spécifier le bon chemin vers le fichier. (ref)

Un autre problème rencontré qui peut ne pas être votre cas,mais autant le signaler au cas où, lors du premier appel de la commande ./manage.py build_solr_schema le fichier schema.xml généré contenait des définitions de champs erronés :

<field name="" type="" indexed="True" stored="True" multiValued="" />

<field name="" type="" indexed="True" stored="True" multiValued="" />

Un nouvel appel à build_solr_schema a cette fois généré un fichier valide, une fois copié sur le serveur solr l'indexation fonctionne, et on peut désormais mettre à jour l'index avec manage.py rebuild_index de même que celui-ci se met à jour lors de l'import des objets dans la base.

Suivant votre configuration réseau vous pourrez vouloir changer le port ou l'interface d'écoute, cela s'effectue dans le fichier /etc/default/jetty ; il ne reste plus enfin qu'à redémarrer jetty.

Ce ne fût pas sans peine, mais au final l'indexation fonctionne et désormais tous les utilisateurs peuvent indexer leurs documents sans problèmes de permissions.

mercredi, décembre 12 2012

Passer le temps en geekant

Un inconvénient d'être freelance et que l'on a parfois du temps chômé entre les missions, l'avantage quand on est geek c'est que cela donne du temps pour geeker :-) J'ai donc profité de ces derniers jours pour assouvir mon envie de jouer avec icecast ; depuis toujours passionné de radio il me tardait de trouver l'idée qui me permettrait de créer un projet autour de cet outil. Deux autres éléments ont été à l'origine de mon idée, tout d'abord la découverte de l'excellente radio http://www.ledjamradio.com/ puis plus récemment de jukebox. Je serais presque jaloux de ledjam d'avoir eu l'idée de cette radio et du système de vote, mais je le suis totalement de leur jingles ; là vous devez faire une pause et lancer la radio dans un onglet. En ce qui concerne Jukebox j'ai apprécié l'interface mais beaucoup moins l'architecture qui en fait au final un outil un plutôt instable (notamment à cause de libshout).

Cette introduction faite il est temps de lâcher le morceaux, j'ai utilisé ces 4 derniers jours à coder Calorine (jeu concours : trouver ce qui a inspiré ce nom), la base de l'idée avoir une radio communautaire, dédiée à un groupe restreint comme le serait par exemple un canal IRC Écouter une radio à plusieurs même dans des lieux physiques différent créé une ambiance de groupe qui se rapproche de la sensation d'équipe dans un bureau commun, c'est très agréable de pouvoir chambrer un peu les collègues sur leurs goûts musicaux du vendredi (ils se reconnaîtront).

Venons-en aux fonctionnalités de Calorine, stream d'une playlist composées à plusieurs de façon communautaire sans hiérarchie, ça c'est la base. La radio dispose d'un pool de fichier ogg/vorbis dans lequel l'auditeur pioche, il ajoute ses titres à la playlist commune. Tout un chacun peut également consulter la playlist en cours et ajouter un vote à un titre, les titres avec le plus grand nombre de votes sont joués en premier, viennent ensuite les titres les plus anciens dans la playlist, et enfin si celle-ci est vide un titre aléatoire est joué. On a pour faire cela une interface écrite en Django ainsi qu'un bot IRC qui annonce le titre joué sur le canal et permet d'ajouter un titre à la playlist en effectuant une recherche full-text sur les ID3 du fichier musical.
Sur le principe technique, les path des fichiers sont stockés dans la base de données utilisées par l'app django. Icecast2 stream le flux audio, ices2 alimente le flux avec le mode script, ices2 appelle un script pour obtenir le chemin du fichieir audio à lire. J'ai préféré cette méthode de couplage ices/icecast plus robuste que d'utiliser libshout comme le fait Jukebox, cela donne aussi une totale indépendance entre l'ihm et le flux radio. L'indexage full-text est lui réalisé avec Haystack et le backend whoosh (des tests de solR sont en cours). Dans le repo il y 2 scripts qui permettent pour l'un d'alimenter la base de données (fill.py) et pour l'autre de convertir un répertoire de fichier mp3 en ogg (covert.py).
Comme tout projet de 4 jours il y a des petits bugs partout et beaucoup de chose à finir, mais c'est déjà fonctionnel et nous permet de nous faire mutuellement découvrir des titres.
Si vous souhaitez monter votre radio communautaire tout est dispo sous GPL sur github.

- page 1 de 2