Ami développeurs et autres, bonjour !
Je vais tenter de faire un petit résumé et une petite analyse de ce qui s'est passé avec notre dernière bêta en date. Avant toute chose, je tient à préciser que cette analyse n'est pas exhaustive, et qu'elle n'engage que moi. Notre DTC préféré est la personne la plus à même à confirmer/infirmer ce que je raconte le cas échéant.
Et on y va.
Les performances et le "front"
(j'ai mis front entre guillemet parce que ça revient souvent à une histoire de back)
Et en fait, toute cette histoire n'as pas commencé avec cette release, mais avec la précédente. Il y a une vingtaine de jours, notre DTC adoré nous a fait remarqué que depuis le passage à la version précédente, les performances étaient vachement tirées vers le bas. Ce qui a déclanché ce problème est pas clair et est un mélange du passage à Django 1.7 et/ou autre chose.
Les causes annoncées sont à peu près listée dans le ticket donné en lien plus haut. En vrac:
- Le morceau de template
badge.part.html
. Avec, en toile de fond, un problème encore plus profond qui est celui de la séparation entre la classeProfile
etUser
, la première étant à nous, la seconde à Django, donc le besoin quasi systématique d'une requête pour récupérer l'un ou l'autre (en l'occurence, ici c'estProfile
). Pour pas arranger les choses, on a réussi à désyncroniser les deux classes, ce qui cause, entre autre, des problèmes avec l'API (et, si je suis bien, la décision de retarder l'arrivée de l'API des MPs) - Le morceau de template
message.part.html
qui, non content de contenir la précédente, rajoute des requêtes en masse pour la récupération des +1/-1 et qui fait un nombre proprement incroyable de requêtes liées à de la vérification de droits (ce qui est particulièrement ridicule et facile à régler: au lieu de vérifier à chaque message que l'admin est bien un admin, on le fait une fois au début) - Le code front est parsemé de
perms.machin.bidule
pour vérifier que l'utilisateur a bien les droits qu'il prétend avoir. C'est une requête à chaque fois. Et c'est un machin qu'on retrouve dans plein de{% if %}
! side-effect, naviguer sur le site en temps qu'admin vous fait automatiquement choper plein de requêtes supplémentaires du genre, ZdS est pas staff-friendly - La template
/forum/base.html
, qui souffre en fait d'une absence de données renvoyées par le back et qui compense par énormément de requêtes. La partie qui pose plus particulièrement problème est celle qui gère les messages suivis: pour chacun d'eux, la template doit récupérer le dernier message lu, ainsi que vérifier que ledit message est lu ou pas (avec le templatetagis_read
, qui finit par refaire la même requête, basiquement). La solution à ce problème est d'aller rajouter des morceaux de requêtes dans le bon templatetag - Même problème dans un autre templatetags, celui de la récupération des MPs, le bien nommé
interventions_privatetopics
: une fois encore, pour un MPs non lu, c'est une requête pour récupérer le dernier message et son auteur. Par contre, sur celle là, il va falloir que quelqu'un trouve le courage de faire de la magie noire, parce qu'il y a une grosse requête SQL inscrite en dur. - Et d'autres, j'imagine, mais c'est les plus gros que j'aie pu débusquer. J'imagine que si on règle ça, on y verra déjà un peu plus clair dans ce que la django toolbar nous sort. Instinctivement, vu les deux derniers points, j'aurais tendance à dire que le templatetags qui récupère les notifs se comporte de manière semblable, mais j'accuse sans preuve.
Et là, Spacefox me répondra probablement "La consommation du site, ce n'est pas que le nombre de requêtes, loin de là.", et il aura probablement raison. D'ailleurs, cette réponse est tellement intéressante que je la cite de suite:
Je me permet d'appuyer fortement le second point: le code en est carrément infesté, et tout ce que j'ai noté plus haut est relié à ce problème de près ou de loin. Par contre, s'en passer va être difficile: pour avoir l'info, il faut quand même faire la requête à un moment. Donc la question, c'est où ?
La ZEP-04
Je ne ferais jamais assez les louanges de la ZEP-04, c'est un excellent travail, et de loin. D'ailleurs, les développeurs de la ZEP-04 n'y sont globalement pour rien. Avant de recevoir des tomates trop mûres dans la figure, laissez moi expliquer le pourquoi de ce titre, donc.
Contrairement à ce qu'on pourrait croire, la ZEP-04 ne fait pas tant de requêtes supplémentaires que ça. Je m'explique: basiquement, la liste des articles et des tutos, on l'avait déjà, la récupération du nombre de commentaire et du dernier commentaire pour les articles, on l'avais déjà, et grosso modo, les seuls trucs nouveaux sont les "unes" (featured items) et la liste des derniers topics en date. Rien de vraiment neuf sous le soleil, donc, sauf que tout ces points sont des nids à problèmes énoncés précédemment : get_*()
générant des requêtes supplémentaires et ainsi de suite.
D'où …
La vraie fausse solution, le cache, et pourquoi c'est parti en cacahuètes
… D'où l'idée un peu folle de Spacefox d'intégrer un cache aux éléments couteux de la page d’accueil, pour calmer les statistiques de Munin qui s'affolaient complètement:
Sauf que le front est, en plus d'être infesté de get_*()
vicieux, ultra-factorisé (Spacefox vous dira "trop dynamique"). Il semblerait donc que la factorisation, c'est pas très cache-friendly.
Deux choses à séparer, là:
- Et d'un, certaines templates (souvent rangées à cet effet dans un dossier
/includes/
) sont appelées avec des paramètres. Pour certaines, (par exemplemessage.part.html
), ce nombre de paramètre est proprement exorbitant (mais ce n'est pas le seul exemple). Le problème est que ces paramètres ne sont écrits nuls part, il faut les deviner en lisant le code (un jeu qui peu vite devenir lassant, et il semblerait que notre bon DTC a un peu moins de patience que moi, et je le comprend). Ce qui pose, d'une manière ou d'une autre, le problème de la documentation front, qui a déjà fait débat par le passé (et ce n'est qu'un exemple), pour un résultat probablement pas très satisfaisant si on en crois ce que je viens de dire. Notons les efforts de Situphen en ce sens, initiative plus que salutaire puisque … Tout est à faire. Sauf que cette doc n'as pas de standard (comme autodoc), ce qui rend la chose très difficile. Bref, tout ça pour dire que choisir des clés de caches devient difficile. - Les templates s'appelent les unes les autres. Une fois encore, c'est un comportement souhaité quand on fait un minimum de factorisation. Il semblerait donc que ça ne soit pas souhaité dans ce cas-ci. Ça, c'est à traiter au cas par cas. Quand on sais que le moteur de template est un peu lent, il semblerait que ce soit un comportement à éviter un maximum (même si on sais souvent pas faire autrement).
On en est donc arrivé à des PRs un peu rustines comme celle là, ou on règle un problème tout bête: évidement, le lien qui envois au dernier commentaire lu n'est pas le même d'un utilisateur à un autre. Et pourtant, dans cette PR là, tout le problème du cache est résumé (et j'imagine que c'est ce que notre DTC appelle "trop dynamique"). Il est évident que ce lien ne peut pas être mis en cache, puisqu'il change d'un utilisateur à un autre. Et pourtant, le retirer enlèverait une fonctionnalité au site. Et ainsi de suite.
Dans tout les cas, l'inclusion de ce cache a entraîné une montagne de problème du même genre, à cause d'un front qui semble mal pensé pour y intégrer un cache tel quel (et comment lui en vouloir, puisque ça n'as pas été pensé à la base pour ça).
Du coup, on tend à l'exclure jusqu'à nouvel ordre.
À noter ceci dit qu'on a déjà du cache sur d'autres éléments, donc TOUT le cache ne disparais pas, simplement celui qui a tenté d'être mis en place cette fois-ci.
Bonus, ou parce que quand même, tout n'est pas la faute du front (loin de là)
Pire que les requêtes en base de donnée, il faut quand même que je mentionne que l'I/O est un sujet sensible. À l'heure d'aujourd'hui, le back des articles et tutoriels est proprement catastrophique à ce sujet: le recourt systématique à Git (parfois plusieurs fois pour la même chose) y est probablement aussi pour quelque chose dans les problème de performance. Ceci étant dit, cette partie là n'as pas beaucoup évolué ces derniers temps pour des raisons évidentes.
Comme dirait artragis, #ilovezep12, on est dessus.
En conclusion
Si je devais résumer tout les points qu'il est important de traiter, les voici:
- Il faut qu'on repasse dans notre code pour optimiser nos requêtes. C'est peut être que l'arbre qui cache1 la foret, mais c'est déjà un pas en avant.
- Il faut qu'on repense une partie du code pour éviter les
get_*()
qui font des requêtes supplémentaire gratuitement. Avec cette question: à quel moment récupérer l'info: dans le back en temps que paramètre passé à la template ? Dans le front avec{% set machin as truc %}
au bon endroit ? - quid d'une documentation des templates ? Est ce que c'est possible ou complètement délirant ?
- Si on veut mettre du cache, il faut qu'on repense des morceaux de front en fonction. Est ce que c'est vraiment nécessaire, ceci dit, vu la montagne de problèmes que ça a apporté cette fois-ci ?
C'est à vous !
-
c'est nul. Vraiment. ↩