Anatomie d'un forum
Posté : 29 juil. 2007, 13:25
Suite à ce post, et puisque personne ne semble vouloir se lancer, je me suis décidé à me dévouer et poster quelque chose.
J'ai beaucoup de théories sur le design d'un forum (que je teste parfois sur vous à votre insu
), particulièrement de son aspect technique. Aujourd'hui, je vais partager avec vous quelques observations sur une des requêtes les plus exécutées par un logiciel de forum : récupérer la liste des sujets ("viewforum", ci-après). Pour les besoins de mes exemples, j'utiliserai le schéma de phpBB 2, mais le problème est commun à la plupart des logiciels de forum. De plus, j'utiliserai librement certains anglicismes (topic, post) parce que leur équivalent français m'irrite 
Alors, cette requête viewforum, qu'est-ce qu'elle a de particulier ?
C'est l'une des pages les plus vues.
Je n'ai pas de stats récentes à ce sujet mais la dernière fois que j'ai vérifié elle représentait plus de 25% des impressions. C'était avant que Firefox (et IE ?) n'utilise systématiquement le cache lors du retour en arrière (bouton Retour) donc ce nombre a dû baisser, mais il est certainement resté conséquent.
Elle est pratiquement incachable
La table de topics est continuellement mise à jour par les nouveaux posts, voire même la lecture des anciens ! (conversion de la charge de lecture en charge d'écriture à cause de la mise à jour du compteur de vues... mais ce sera pour une autre fois) Résultat : vous pouvez oublier le query cache de MySQL.
Elle s'alourdit continuellement
La liste des topics s'allonge de jour en jour, à moins de régulièrement délester ou archiver ses sujets. La conséquence est qu'il faut trier de plus en plus de résultats. De plus, accèder aux plus anciens topics demander de passer (skip) de nombreux résultats.
En parlant de tri justement...
Récupérons la liste des topics du forum 3 pour voir
Les topics sont triés par topic_type DESC pour placer les post-its (stickies) en haut de page, puis par topic_last_post_id pour les classer par ordre chronologique d'insertion dans la base. Que nous dit EXPLAIN ?
Ouch, on trie ~2527 lignes. Heureusement, le tri s'effectue en mémoire et en 0.04s sur mon ordinateur. Qu'est-ce qu'on peut y faire ? Ben par exemple on pourrait utiliser un index qui couvre toute la clause WHERE :
Résultat :
Bon, c'est un peu mieux sur le papier et l'exécution est tombée à 0.03s, mais on continue à trier plusieurs milliers de lignes. On peut y remédier en étendant l'index pour qu'il couvre également la clause de tri :
Plus de tri du tout, et l'exécution descend à 0.00s (moins d'un centième de seconde). Est-ce la panacée ? Loin de là. C'est un index super-spécialisé, donc il ne vous servira probablement qu'à afficher viewforum. Ensuite, si vous voulez classer les topics par ordre chronologique normal les deux colonnes de tri ne seront pas dans le même sens (topic_type DESC, topic_last_post_ts ASC) et MySQL ne pourra pas utiliser l'ordre de l'index. En plus de ça il ne résoud pas le problème de l'accès aux plus anciennes page, il ne fait que diminuer la vitesse à laquelle les performances se dégradent. Quelques pistes : on remplace topic_last_post_id par topic_last_post_ts, un champs qui contient le timestamp du dernier post. Les topics restent triés par ordre chronologique et grâce au timestamp, on peut faire un "cut", c'est-à-dire limiter la période des topics, par exemple "n'afficher que les sujets datant de moins d'un mois". Ça aide pour les cas où MySQL a malgré tout à classer les topics par un filesort, mais ça ne règle pas le problème d'accès aux anciennes pages. On peut utiliser des tricks, comme par exemple scanner la table en partant de la fin. Ainsi, dans un forum comportant 3000 topics :
...et...
...renverront les mêmes résultats (topics 2971 à 2980, sachant que le 20 de la première requête correspond à 3000 - 2970 - 10), mais pas dans le même ordre. On peut utiliser PHP ou une sous-requête (très rapide) pour les reclasser dans le bon ordre. À ma connaissance, la première personne a avoir parlé publiquement de cette technique était BartVB sur les forums phpBB. Mais là encore, ça ne fait que repousser le problème vers les pages du milieu, équidistantes du début et de la fin du forum.
Une autre façon de procéder serait d'abandonner le système de numérotation de page tel qu'il est utilisé sur la plupart des forums. D'ailleurs, sur les forums les plus actifs, ce système montre rapidement ses limites : le temps de lire la première page et cliquer sur le lien vers la page 2, de nouveaux topics ont été postés et vous retombez sur ceux que vous venez juste de lire ! À la place, on pourrait imaginer de naviguer par date. Ainsi, plutôt que de lister les sujets de "la page 2", vous demandez au forum de lister les topics "plus anciens que ceux en cours" (viewforum.php?page=2). Par exemple, "plus ancien que 29-07-2007 14:31:12" (viewforum.php?older=1185712272). Là, plus aucune chance de retomber sur les mêmes sujets. De plus, l'index couvre réduit encore plus les résultats possibles (key_len passe de 3 à 7). En revanche, il existe au moins un défaut à cette solution : un timestamp n'est pas unique, si deux sujets partagent le même timestamp, il se pourrait qu'un des deux se retrouve coincés "entre deux pages" si son jumeau se trouve être le dernier de la page. De plus, il devient impossible de numéroter les pages donc il faut trouver un autre repère pour les utilisateurs pour éviter qu'ils se sentent perdus.
La suite plus tard...
J'ai beaucoup de théories sur le design d'un forum (que je teste parfois sur vous à votre insu
Alors, cette requête viewforum, qu'est-ce qu'elle a de particulier ?
C'est l'une des pages les plus vues.
Je n'ai pas de stats récentes à ce sujet mais la dernière fois que j'ai vérifié elle représentait plus de 25% des impressions. C'était avant que Firefox (et IE ?) n'utilise systématiquement le cache lors du retour en arrière (bouton Retour) donc ce nombre a dû baisser, mais il est certainement resté conséquent.
Elle est pratiquement incachable
La table de topics est continuellement mise à jour par les nouveaux posts, voire même la lecture des anciens ! (conversion de la charge de lecture en charge d'écriture à cause de la mise à jour du compteur de vues... mais ce sera pour une autre fois) Résultat : vous pouvez oublier le query cache de MySQL.
Elle s'alourdit continuellement
La liste des topics s'allonge de jour en jour, à moins de régulièrement délester ou archiver ses sujets. La conséquence est qu'il faut trier de plus en plus de résultats. De plus, accèder aux plus anciens topics demander de passer (skip) de nombreux résultats.
En parlant de tri justement...
Récupérons la liste des topics du forum 3 pour voir
Code : Tout sélectionner
EXPLAIN
SELECT *
FROM phpbb_topics
WHERE forum_id = 3
AND topic_type IN (0, 1)
ORDER BY topic_type DESC, topic_last_post_id DESC
LIMIT 10;Code : Tout sélectionner
+----+-------------+--------------+------+---------------------+----------+---------+-------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------------+----------+---------+-------+------+-----------------------------+
| 1 | SIMPLE | phpbb_topics | ref | topic_type,forum_id | forum_id | 2 | const | 2527 | Using where; Using filesort |
+----+-------------+--------------+------+---------------------+----------+---------+-------+------+-----------------------------+Code : Tout sélectionner
ALTER TABLE phpbb_topics DROP INDEX forum_id, ADD INDEX viewforum (forum_id, topic_type);Code : Tout sélectionner
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-----------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-----------------------------+
| 1 | SIMPLE | phpbb_topics | range | topic_type,viewforum | viewforum | 3 | NULL | 2308 | Using where; Using filesort |
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-----------------------------+Code : Tout sélectionner
ALTER TABLE phpbb_topics DROP INDEX viewforum, ADD INDEX viewforum (forum_id, topic_type, topic_last_post_id);Code : Tout sélectionner
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-------------+
| 1 | SIMPLE | phpbb_topics | range | topic_type,viewforum | viewforum | 3 | NULL | 2496 | Using where |
+----+-------------+--------------+-------+----------------------+-----------+---------+------+------+-------------+Code : Tout sélectionner
SELECT *
FROM phpbb_topics
WHERE forum_id = 3
AND topic_type IN (0, 1)
ORDER BY topic_type DESC, topic_last_post_id DESC
LIMIT 10
OFFSET 2970;Code : Tout sélectionner
SELECT *
FROM phpbb_topics
WHERE forum_id = 3
AND topic_type IN (0, 1)
ORDER BY topic_type ASC, topic_last_post_id ASC
LIMIT 10
OFFSET 20;Une autre façon de procéder serait d'abandonner le système de numérotation de page tel qu'il est utilisé sur la plupart des forums. D'ailleurs, sur les forums les plus actifs, ce système montre rapidement ses limites : le temps de lire la première page et cliquer sur le lien vers la page 2, de nouveaux topics ont été postés et vous retombez sur ceux que vous venez juste de lire ! À la place, on pourrait imaginer de naviguer par date. Ainsi, plutôt que de lister les sujets de "la page 2", vous demandez au forum de lister les topics "plus anciens que ceux en cours" (viewforum.php?page=2). Par exemple, "plus ancien que 29-07-2007 14:31:12" (viewforum.php?older=1185712272). Là, plus aucune chance de retomber sur les mêmes sujets. De plus, l'index couvre réduit encore plus les résultats possibles (key_len passe de 3 à 7). En revanche, il existe au moins un défaut à cette solution : un timestamp n'est pas unique, si deux sujets partagent le même timestamp, il se pourrait qu'un des deux se retrouve coincés "entre deux pages" si son jumeau se trouve être le dernier de la page. De plus, il devient impossible de numéroter les pages donc il faut trouver un autre repère pour les utilisateurs pour éviter qu'ils se sentent perdus.
La suite plus tard...