Améliorations des performances du forum : oui mais comment ?

L'auteur de ce sujet a trouvé une solution à son problème.
Staff
Auteur du sujet

Salut les agrumes !

Le constat

Je pars du constat (visible sur les stats Munin et GA) que nos pages de forum sont à la fois les plus visitées et les plus lourdes à charger.

En particulier, toutes ces pages font une quantité délirantes de requête, dont certaines pour des données qui sont systématiquement re-calculées à chaque affichage. En première approche, je pense à :

  • Liste des forums : pour chaque catégorie, on recalcule :
    • Nombre de sujets
    • Nombre de messages
    • Date du dernier message
    • Titre du topic avec le dernier message
    • Lien vers le dernier message
  • Catégorie de forum : pour chaque sujet affiché, on recalcule :
    • Nombre de réponses
    • Date du dernier message
    • Lien vers le dernier message
    • Nom de l'auteur de ce message
    • Lien vers l'auteur du dernier message
    • (Les données du 1er message ne sont pas récupérées dans des requêtes dédiées)
  • Page de forum : pour chaque message affiché, on recalcule :
    • Le badge de l'auteur

La conséquence, c'est qu'on provoque systématiquement toutes les requêtes suivantes :

  • Liste des forums : pour chaque catégorie :
    • Compter les sujet
    • Compter les messages
    • Récupérer séparément l'objet Post du dernier message
  • Catégorie de forum : pour chaque sujet affiché :
    • Compter les réponses
    • Récupérer séparément l'objet Post du dernier message
    • Récupérer séparément l'objet Profile du dernier posteur
  • Page de forum : pour chaque message affiché :
    • Une ou plusieurs requêtes spécifiques pour récupérer le badge (recalculé en fonction des droits du membre affiché)

Pour un membre connecté, on a d'autres problématiques qui viennent s'ajouter (en particulier la gestion des messages lus/non lus) mais là ça devient beaucoup plus compliqué à gérer, donc on ignore le problème pour l'instant.

Les requêtes en elle-même sont très rapides, mais leur multiplication fait qu'on passe un temps important en traitement Python sur ces requêtes (préparation, envoi au SGBD, récupération, etc).

Ça fait aussi que ces pages sont très sensibles aux lags de la BDD (on a aucune maîtrise sur les accès disques, et notre VPS fait un peur n'importe quoi de ce côté).

Comment régler le problème ?

Je vois 2 solutions sur lesquelles j'aimerais votre avis

Dé-normaliser le modèle

Il s'agit de mettre une copie des informations systématiquement recalculées dans la table "parente", comme c'est fait aujourd'hui avec les +1 / -1. Ces informations sont mises à jour au moment de l'enregistrement de l'enfant.

Exemple : les catégories contiendraient le nombre de topic, de messages et les informations sur le dernier message. Les forums contiendraient le nombre de message et les informations sur le dernier message et son auteur.

C'est assez simple à faire (sauf la migration de données pour dénormaliser le badge des membres). C'est simple à gérer parce que l'affichage se contente d'afficher une donnée de la base. Les pages ont un affichage optimisé par construction.

Par contre ça veut dire que les écritures sont beaucoup plus lourdes : par exemple, tout post de message irait mettre à jour la table des catégories, ce qui peut provoquer des problèmes.

Aussi, un bug dans une mise à jour risque de provoquer une désynchronisation des données dénormalisées, ce qui est chiant à corriger.

Mettre du cache sur ces informations spécifiquement

Là il s'agit de mettre en cache non pas les pages complètes (on ne peut pas vraiment, puisqu'elles sont très volatiles) mais uniquement les bouts d'informations qui nous intéressent.

C'est un peu plus compliqué à mettre en place, mais on s'affranchit complètement des problèmes d'engorgement en écriture et des désynchronisation.

L'utilisateur qui arrive sur une page avec un cache vide a une page un peu moins performante, mais comme notre site reste utilisable sans ce cache (signe d'un cache bien utilisé), ce n'est pas très gênant.

Le principal inconvénient c'est qu'on doit gérer un cache : ça implique d'éventuels problèmes de cache, et donc des bugs chiants s'ils arrivent dans ce système. Cela dit, là encore c'est sur des données non critiques donc au pire, c'est pas très grave.

Que pensez-vous de ces idées ?

Dans ces cas là, le cache me semble toujours plus pérenne. Même si c'est souvent une véritable tannée à mettre en place (et encore plus à tester de manière automatisé). Par contre en règle général c'est assez simple (dans les deux cas), de mesure le gain en performances.

Je pense qu'un des avantages du cache c'est que la migration peut peut-être se faire de façon plus progressive, et surtout : qu'il entraîne généralement moins de régression. (si implémenté comme il faut).

Quand on dénormalise le modèle, on s'expose au problème de régression. Et pour le coup c'est le seul et unique point où je ne suis pas d'accord avec ton analyse : c'est là que je vois des "bugs chiants". Du style "Ah oui mais l'info dans la table est pas mise à jour dans ce cas merdique bien précis qu'on avait pas pensé" -> reproductible à l'infini.

Autre problème de la dénormalisation du modèle : le plat de spaghetti. Suivant l'état des "managers" ou "services" ou "DAO" ou peu importe : les intercepteurs qui sont censés venir mettre à jour les données dénormalisées.

J'ai travaillé sur des projets dans lesquels c'était extrêmement bien implémenté. A l'aide d'intercepteurs (Seam, AOP, …) ça doit s'implémenter de façon élégante en Python avec des décorateurs (un tant soit peu génériques) sous réserve que le code de mise à jour (post forum, …) soit correctement écrit et mutualisé(able). Exemple alakon : tu postes un message par API, paf t'as pas la mise à jour du nombre de réponses dans le topic parce que c'est pas le même code, blablabla.

Du coup à travers ma modeste expérience du sujet, je partirais sur un cache le plus simple possible (i.e. invalidé uniquement temporellement, jamais manuellement) et le monitererai pour voir comment il évolue (i.e. combien de hits pour combien d'écritures, les infos sont-elles pertinentes ?)

En gros : dans une phase bêta (en pré-prod ?) je regarderai le pourcentage de hits renvoyant une info pas à jour (sachant que c'est loin d'être la mort d'avoir une stat pas à jour pour le nombre de sujets d'un forum) vs. le nombre de hits total.

Et de là, j'en déduirai s'il est nécessaire de venir invalider le cache "manuellement" sur certaines mises à jour critiques.

En tout cas c'est un cas complexe et une réflexion très intéressante. J'engage tout le monde à y réfléchir (y compris, et surtout, des étudiants en informatique qui s'orientent vers le web) c'est un cas d'école.

Happiness is a warm puppy

+3 -0

J'ai travaillé sur des projets dans lesquels c'était extrêmement bien implémenté. A l'aide d'intercepteurs (Seam, AOP, …) ça doit s'implémenter de façon élégante en Python avec des décorateurs (un tant soit peu génériques) sous réserve que le code de mise à jour (post forum, …) soit correctement écrit et mutualisé(able). Exemple alakon : tu postes un message par API, paf t'as pas la mise à jour du nombre de réponses dans le topic parce que c'est pas le même code, blablabla.

Javier

C'est fournit dans Django sous la forme de signaux, et de recivers pour les signaux. Donc normalement pas de problème pour la mutualisation du code.

Mon Github — Tuto Homebrew — Article Julia

+1 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte