MySQL: flood, performances & max_questions

De temps en temps, je vois passer des gens qui tombent sur cette erreur et se demandent de quoi il s'agit:

ERROR 1226 (42000): User 'xxx' has exceeded the 'max_questions' resource (current value: yyy)

Qu'est-ce que c'est ? ; Causes ; Solutions: c'est parti.

De quoi s'agit-il ?

Il s'agit de notre système anti-flood. Quand je dis «anti-flood», je parle d'une limite *vraiment* haute qui sert à protéger la plateforme d'un comportement *vraiment* anormal. Si vous faites partie des 0.01% d'utilisateurs à qui c'est déjà arrivé (ce n'est pas une approximation mais une vraie statistique, prise au moment où je vous écrit), alors il faut faire quelque-chose. Je parle des solutions un peu plus bas.

Qui est concerné ?

Vous ne retrouverez cette erreur que sur les bases de données livrées avec vos hébergement web, appelées également «SharedSQL» ou «Bases de données MySQL mutualisées». Mais ça ne vaut pas dire pour autant que passer sur une Web Cloud Database, une Public Cloud Database, un serveur dédié ou un autre hébergeur corrigera le problème. Eux aussi vont se faire noyer par le comportement anormal, et la perf ne sera pas au rendez-vous.

max_questions?

MySQL permet de limiter la fréquence du nombre de requêtes faites par un utilisateur: https://dev.mysql.com/doc/refman/8.0/en/user-resources.html Le message dont on parle est ce que réponds MySQL quand cette limite a été dépassée.

C'est sur cette fonctionnalité que s'appuye notre système anti-flood, mais on l'a améliorée. En effet, la fonctionnalité telle quelle permet de définir un nombre maximum de requête… par heure. Prenons l'exemple où on limiterait un utilisateur à 60000 requêtes par heure.

14:00 : Une heure pile, MySQL reset le compteur. L'utilisateur a fait 0 requête cette heure-ci.
14:20 : 20 minutes se sont écoulées, et le compteur en est à 1234. Pour une raison ou pour une autre, l'utilisateur fait 100000 requêtes d'un coup (ce qui n'est pas normal, on va voir un peu plus loin comment c'est possible). Les 58766 premières passent, les 41234 suivantes sont bloquées.
De 14:20 à 14:59 : La limite a été atteinte pour cette heure-ci. Toutes les requêtes sont bloquées en attendant 15:00, heure à laquelle le compteur va être remis à 0.

Ce comportement n'est pas satisfaisant: le site branché à la base de données est cassé pendant 40 minutes, alors que le comportement anormal est passé. Les requêtes légitimes sont bloquées.

Ce qu'on a fait pour améliorer ça et transformer la limite de requêtes par heure en limite de requêtes par minute, c'est qu'on définit une limite 60 fois plus petite, mais on reset le compteur toutes les minutes. Pour reprendre notre exemple, au lieu de définir une limite à 60000 et attendre que MySQL reset le compteur toutes les heures, on va définir une limite à 1000 et remettre le compteur à 0 toutes les minutes. Reprenons notre exemple:

14:20:10 : Le compteur est à 9 (le nombre de requêtes faites de 14:20:00 et 14:20:10). C'est l'heure du comportement anormal, les 100000 requêtes illégitimes arrivent. La majorité se fait bloquer. Plus rien ne passe jusque 14:21.
14:21:00 : Le compteur est à 0. Le comportement anormal est passé, le site va rester UP.

Causes et solutions

Les cas que je décris ci-dessous sont des cas qu'on a constaté de multiples fois. La liste n'est pas exhaustive, mais elle couvre une grande majorité des cas.

(la suite dans le prochain commentaire)