Archive for December, 2013

Bug MySQL : pas de cache avec des tables InnoDB et un nom de base/table foo-bar

Monday, December 2nd, 2013

L’équipe Evolix a récemment découvert un bug assez incroyable : les versions 5.1 à 5.6.8 de MySQL ne gèrent pas le Query Cache (un mécanisme essentiel) sur les tables InnoDB si le nom de la table ou base contient un caractère particulier, notamment le tiret () !! Ce n’est pas une véritable découverte car le bug a été corrigé pour la version 5.6.9 en décembre 2012, mais il a été peu « médiatisé » et surtout il impacte de nombreuses distributions Linux. Concrètement si vous avez Debian (Squeeze/Wheezy/Testing/Sid) ou Ubuntu (toutes les versions depuis 4 ans) et vous utilisez le paquet mysql-server, toutes vos bases ou tables nommées foo-bar sont impactées. Toutes vos requêtes sensées être servies par le cache MySQL ne le sont pas, et il en découle des problèmes de performance (parfois très significatifs). Au passage, pour vérifier qu’une requête MySQL utilise bien le Query Cache, vous pouvez observer le compteur de hits via show status like ‘Qcache_hits’ (parfois difficile sur des serveurs en production, mais vous avez bien sûr des serveurs de préproduction ;-).

Mise en évidence du bug avec un nom de base foo-bar :

mysql> create database `foo-bar`;
mysql> create table `foo-bar`.baz (a int) engine=InnoDB;
mysql> insert into `foo-bar`.baz values (1);
mysql> select * from `foo-bar`.baz;
mysql> show status like 'Qcache_hits';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Qcache_hits   | 42     |
+---------------+--------+
mysql> select * from baz;
mysql> show status like 'Qcache_hits';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Qcache_hits   | 42     |
+---------------+--------+

Mise en évidence du bug avec un nom de table baz-qux :

mysql> create database foobar;
mysql> create table foobar.`baz-qux` (a int) engine=InnoDB;
mysql> insert into foobar.`baz-qux` values (1);
mysql> select * from foobar.`baz-qux`;
mysql> show status like 'Qcache_hits';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Qcache_hits   | 42     |
+---------------+--------+
mysql> select * from foobar.`baz-qux`;
mysql> show status like 'Qcache_hits';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Qcache_hits   | 42     |
+---------------+--------+

Si vous n’êtes pas encore parti en courant vérifier le nom de vos bases et tables MySQL/MariaDB, je vais vous expliquer comment ce bug a été découvert et pourquoi cela illustre bien le travail d’infogérance d’Evolix. En effet, un abonnement à l’infogérance est une sorte d’assurance : outre les opérations visibles (surveillance 24/24, veille technologique, mises-à-jour, support technique), c’est aussi la garantie que l’on mettra tout en œuvre si il vous arrive un incident. Pendant plusieurs mois/années vous n’aurez peut-être pas d’incident sérieux (et tant mieux pour tout le monde), mais un jour vous aurez besoin d’une attention de plusieurs heures voire plusieurs jours. C’est ce qui est arrivé ces dernières semaines avec ce bug MySQL : un client d’Evolix a signalé une lenteur sur certaines pages d’un site d’e-commerce suite à une migration de serveurs. Après pas mal d’analyses, nous n’étions pas convaincus, la lenteur s’expliquait par des milliers de requêtes SQL faites sur chaque page, et surtout cette lenteur était également présente sur l’ancien serveur. Néanmoins, nous avons fini par découvrir que sur un très vieux serveur en Debian Lenny, les pages étaient moins lentes. Après de nombreux tests (passage en SSD, utilisation de TMPFS pour éliminer les I/O disque, tests sur différents serveurs dont un ultra puissant, strace des process, etc.), nous avons mis en évidence que l’exécution des milliers de requêtes SQL étaient tout simplement plus rapides sur le très vieux serveur en Debian Lenny. Ce constat était un peu effrayant pour nous, et presque tout l’équipe s’est mis sur ce problème. On a identifié que les requêtes n’utilisaient pas le Query Cache, et l’on a donc relu toute la documentation concernant le cache MySQL… mais rien à faire, des requêtes SQL simples n’étaient toujours pas mises en cache. Même résultats avec MySQL 5.1/5.5 ou MariaDB 5.5. Puis l’on a découvert ce fameux bug : la base concernée ayant un tiret dans son nom, on l’a fébrilement renommée et *bingo* les requêtes sont désormais cachées !

En attendant une éventuelle correction du bug par Debian, nous conseillons de renommer vos bases et tables impactées. Au passage, le tiret () est un caractère particulier pour MySQL et il est préférable de prendre l’habitude d’utiliser l’underscore (_) dans le nom de vos bases et tables.