10
Oct/09
8

MySQL Query Cache

La query cache de MySQL joue un rôle important dans la performance de plusieurs sites Web.  Elle a pour avantage d’être  transparente, c’est-à-dire que la ou les applications qui s’en servent n’ont pas besoin d’être modifiées.

J’ai recu la semaine passée la question suivante (je résume):

Je souhaite utiliser une cache pour le menu de mon site afin de le rendre plus performant. Puisque le contenu du menu ne change pratiquement jamais, est-il plus avantageux d’utiliser APC Cache que la Query Cache de MySQL puisque la communication s’effectue selon un schéma comme:

php -> cache_apc (ram)
php -> mysql -> query_cache (ram)

Je souhaite réduire au minimum les requêtes SQL exécutées.

Personnellement, j’utiliserais la cache de MySQL. Tout d’abord, il faut savoir qu’il y a une énorme différence entre les 2 caches.

MySQL Query Cache est centralisée sur le serveur MySQL, c’est-à-dire qu’elle utilise la RAM de la machine du serveur MySQL. Elle possède un mécanisme d’invalidation basique, mais efficace. L’invalidation se produit lorsque des valeurs d’une table sont en cache, et que ces valeurs sont mise à jour par une application ou manuellement par un utilisateur.

APC Cache est centralisée sur le serveur Web, c’est-à-dire qu’elle utilise la RAM disponible sur le serveur qui roule Apache et PHP (en supposant que vous utilisez Apache). Vous serez responsable d’invalider la cache lorsque nécessaire. Si quelqu’un modifie la valeur directement dans MySQL, la cache possèdera la vieille valeur jusqu’à ce qu’un processus l’invalide.

Puisque les données ne changent pratiquement jamais, je ne me casserais pas la tête à réinventer la roue. MySQL fait déjà pour vous ce que APC ferait, sans le moindre effort. De plus, il est plus ou moins vrai de dire que d’appeler la cache correspond à une requête. Oui, la string SQL est nécessairement envoyé à MySQL, mais lorsque celui-ci la reçoit et valide que cette requête est cachée, il retourne immédiatement le résultat sans rien “processer”. C’est comme ça que fonctionne APC aussi, il lui faut un identifiant unique pour associer le résultat, tout comme fait MySQL avec le HASH de la requête.

Les caches (peu importe laquelle) sont tout aussi efficaces avec une petite requête qui consomme peu de ressource qu’avec une grosse qui en demande beaucoup. Il est donc plus avantageux de cacher les processus lourds que les légers.

Ce qu’il faut surtout se soucier lorsqu’on utilise une cache, c’est comment et à quelle fréquence il faut l’invalider. Lorsque la Query Cache de MySQL est activée, le processus de cacher les résultats et de les invalider s’effectue tout seul de manière invisible. Ainsi, d’autres requêtes que vous ne soupçonnez même pas bénéficient de la cache. À l’inverse, il faut modifier le code pour chaque requête que vous souhaitez cacher avec APC.

13
Aug/08
0

Raid, Cache et BBU

Je vous parlais récemment de Sysbench, aujourd’hui je vais vous montrer comment il m’a aidé à résoudre un problème. Tout d’abord, je dois vous expliquer le problème initial. Je reçois un nouveau serveur qui sera le Master de notre environnement de pré-production, une bête dans son genre: Dual Quad Xeon 2Ghz, 32Go de RAM, 4 disques scsi montés en RAID5. Après avoir installé un backup, je lance un script qui fait des modifications massives sur les données dans le but de mesurer à quel point le nouveau serveur est performant.

Surprise ! Le script était excessivement plus lent que d’habitude. J’ai fouillé un peu et j’ai constaté que le IOwait était étrangement élevé. Après quelques recherches infructueuses sur google pour des problèmes de MySQL avec un IOwait élevé, je me suis connecté sur IRC, #MySQL (sur freenode) et j’ai parlé de ma situation. À mon grand étonnement, Clint Byrum (aka SpamapS) possèdait des réponses précises à mon problème. Nous sommes arrivé à la conclusion que la write-cache était probablement désactivée. C’était le cas.

À partir de ce constat, je décide d’utiliser Sysbench pour valider que le problème vient de la cache du RAID.

Les tests:
#1. On fait écrire dans 128 fichiers de 1MB (128MB total), les fichiers sont suffisamment petits pour que tout se fasse en cache.
#2. On fait écrire dans 128 fichiers de 8MB (1GB au total), pour être sûr de remplir la cache au complet
#3. On fait lire et écrire dans 128 fichiers de 1MB (128MB total), les fichiers sont suffisamment petits pour que tout se fasse en cache.
#4. On fait lire et écrire dans 128 fichiers de 8MB (1GB au total), pour être sûr de remplir la cache au complet

TEST #1
./sysbench –test=fileio –init-rng=1 –max-time=60 –max-requests=1000000 –file-num=128 –file-total-size=128M –num-threads=16 –file-test-mode=rndwr run

SANS LA CACHE:
Operations performed: 0 Read, 24804 Write, 31288 Other = 56092 Total
Read 0b Written 387.56Mb Total transferred 387.56Mb (6.4451Mb/sec)
412.49 Requests/sec executed

AVEC LA CACHE:
Operations performed: 0 Read, 241204 Write, 305548 Other = 546752 Total
Read 0b Written 3.6805Gb Total transferred 3.6805Gb (62.657Mb/sec)
4010.04 Requests/sec executed

—-====——–====——–====——–====——–====——–====——–====——–====——–====——–====——–====—-

TEST #2
./sysbench –test=fileio –init-rng=1 –max-time=60 –max-requests=1000000 –file-num=128 –file-total-size=1024M –num-threads=16 –file-test-mode=rndwr run

SANS LA CACHE:
Operations performed: 0 Read, 19501 Write, 24129 Other = 43630 Total
Read 0b Written 304.7Mb Total transferred 304.7Mb (5.077Mb/sec)
324.93 Requests/sec executed

AVEC LA CACHE:
Operations performed: 0 Read, 28702 Write, 35615 Other = 64317 Total
Read 0b Written 448.47Mb Total transferred 448.47Mb (7.4293Mb/sec)
475.47 Requests/sec executed

—-====——–====——–====——–====——–====——–====——–====——–====——–====——–====——–====—-

TEST #3
./sysbench –test=fileio –init-rng=1 –max-time=60 –max-requests=1000000 –file-num=128 –file-total-size=128M –num-threads=16 –file-test-mode=rndrw run

SANS LA CACHE:
Operations performed: 35345 Read, 23568 Write, 67552 Other = 126465 Total
Read 552.27Mb Written 368.25Mb Total transferred 920.52Mb (15.317Mb/sec)
980.30 Requests/sec executed

AVEC LA CACHE:
Operations performed: 70214 Read, 46809 Write, 135896 Other = 252919 Total
Read 1.0714Gb Written 731.39Mb Total transferred 1.7856Gb (30.385Mb/sec)
1944.64 Requests/sec executed

—-====——–====——–====——–====——–====——–====——–====——–====——–====——–====——–====—-

TEST #4
./sysbench –test=fileio –init-rng=1 –max-time=60 –max-requests=1000000 –file-num=128 –file-total-size=1024M –num-threads=16 –file-test-mode=rndrw run

SANS LA CACHE:
Operations performed: 16446 Read, 10964 Write, 28273 Other = 55683 Total
Read 256.97Mb Written 171.31Mb Total transferred 428.28Mb (11.971Mb/sec)
766.15 Requests/sec executed

AVEC LA CACHE:
Operations performed: 41350 Read, 27576 Write, 72158 Other = 141084 Total
Read 646.09Mb Written 430.88Mb Total transferred 1.0517Gb (17.941Mb/sec)
1148.24 Requests/sec executed

Si vous avez pris le soin d’étudier les résultats, vous avez compris que la cache apporte un gain de performance impressionnant ! On s’y attendait. L’impact sur MySQL est considérable. Cependant, il faut savoir que MySQL n’utilise qu’un seul CPU par thread, ce qui n’apporte pas un gain de performance aussi remarquable que les benchmarks lorsqu’un script utilise qu’une seule connexion.

Mon script roulait normalement après avoir activé la cache, sans être plus rapide qu’à la normal. J’étais un peu deçu, mais j’ai vite compris qu’avec plusieurs threads connectés en meme temps le serveur répondait toujours aussi rapidement. J’ai remarqué une nette amélioration lorsqu’il y a plusieurs utilisateurs connectés simultanément. Voici un petit benchmark avec la cache activé, via MySQL, avec 40 threads:

./sysbench –test=oltp –oltp-test-mode=complex –max-time=60 –max-requests=300000 –num-threads=40 –init-rng=1 –oltp-table-size=3000000 –mysql-user=testing –mysql-password=testing –mysql-host=127.0.0.1 run

queries performed:
read: 670964
write: 239630
other: 95852
total: 1006446
transactions: 47926 (798.38 per sec.)
deadlocks: 0 (0.00 per sec.)
read/write requests: 910594 (15169.31 per sec.)
other operations: 95852 (1596.77 per sec.)

Mais activer la cache n’est pas sans danger, le serveur doit posséder une batterie (BBU pour Battery Backup Unit). En cas de panne, cette batterie permet d’assurer la consistance des données sur les disques du RAID. La majorité des softwares pour activer la cache vous avertira qu’il est nécessaire d’avoir une batterie.

En résumé, si vous possédez un serveur avec du RAID, il s’avère quasiment nécessaire d’avoir une batterie et la cache d’activée pour avoir des performances à la hauteur de vos espérances.