2010-01-06 2 views
0

Je lance une application Web moyennement populaire sur Django avec Apache2, mod_python et PostgreSQL 8.3 avec le backend de base de données postgresql_psycopg2. Je rencontre des livelock occasionnels, identifiables quand un processus apache2 consomme continuellement 99% du CPU pendant plusieurs minutes ou plus.Débogage de livelock dans Django/Postgresql

J'ai fait un sur le processus de apache2 -p strace pid, et a constaté qu'il répétait sans cesse ces appels système:

sendto(25, "Q\0\0\0SSELECT (1) AS \"a\" FROM \"account_profile\" WHERE \"account_profile\".\"id\" = 66201 \0", 84, 0, NULL, 0) = 84 
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 
poll([{fd=25, events=POLLIN|POLLERR, revents=POLLIN}], 1, -1) = 1 
recvfrom(25, "E\0\0\0\210SERROR\0C25P02\0Mcurrent transaction is aborted, commands ignored until end of transaction block\0Fpostgres.c\0L906\0Rexec_simple_query\0\0Z\0\0\0\5E", 16384, 0, NULL, NULL) = 143 

Ce fragment exact répète sans cesse dans la trace, et était en cours d'exécution depuis plus 10 minutes avant que je finisse par tuer le processus apache2. (Note: j'ai édité ceci pour remplacer mon fragment précédent de strace avec un nouveau qui montre plein le plein contenu de chaîne plutôt que tronqué.)

Mon interprétation de ce qui précède est que django essaye de faire un contrôle d'existence sur ma table account_profile, mais à un moment donné (avant de commencer la trace) quelque chose s'est mal passé (erreur d'analyse SQL? intégrité référentielle ou violation de contrainte d'unicité? Qui sait?), et maintenant Postgresql renvoie l'erreur "transaction courante avortée". Pour une raison quelconque, au lieu de déclencher une exception et d'abandonner, il ne cesse de réessayer.

Une possibilité est que cela soit déclenché dans un appel à Profile.objects.get_or_create. C'est la classe de modèle qui correspond à la table account_profile. Peut-être qu'il y a quelque chose dans get_or_create qui est conçu pour attraper un ensemble d'exceptions trop large et réessayer? À partir des journaux du serveur Web, il semble que ce livelock puisse être le résultat d'un double-clic sur le bouton POST dans le formulaire d'inscription de mon site. Cette condition est survenue plusieurs fois au cours des derniers jours sur le site en direct, et entraîne un ralentissement important jusqu'à ce que j'intervienne, donc à peu près n'importe quoi autre qu'une impasse infinie serait une amélioration! :)

+0

nitpick technique Unhelpful: ce n'est pas vraiment « livelock » - ce terme fait référence à une situation dans laquelle les deux parties ajustent continuellement leurs propres États pour tenter d'accueillir l'autre, mais ne parvient à avancer plus loin. Mais dans votre cas, PostgreSQL est dans un état d'erreur permanent.J'appellerais ceci est une boucle ou une boucle infinie. –

Répondre

-1

Votre analyse semble assez bonne. De toute évidence, cela ne prouve pas que la transaction a été abandonnée. Je vous suggère de signaler cela comme un bug au projet django ...

1

Cela s'est avéré être entièrement de ma faute. J'ai trouvé l'endroit où l'instruction select (1) as 'a' semblait provenir (dans django/models/base.py) et l'ai piraté pour enregistrer un retraçage, qui pointait clairement mon code.

J'avais du code qui constituait une "clé" unique pour chaque profil. Ces clés sont générées de manière aléatoire, donc parce qu'il y a une possibilité de chevauchement, je l'exécute dans une boucle try/except within while. Mon hypothèse était que la contrainte unique de la base de données entraînerait l'échec de la sauvegarde si la clé n'était pas unique, et je serais en mesure d'essayer à nouveau.

Malheureusement, dans Postgresql, vous ne pouvez pas réessayer après une erreur d'intégrité. Vous devez exécuter une commande COMMIT ou ROLLBACK (même si vous êtes en mode autocommit, apparemment) avant de pouvoir réessayer. J'ai donc eu une boucle infinie de tentatives de sauvegarde échouant où j'ignorais le message d'erreur.

Maintenant, je cherche une exception plus spécifique (django.db.IntegrityError) et exécute un nombre limité de tentatives pour que la boucle ne soit pas infinie.

Merci à tous pour l'affichage/réponse.