La meilleur requête est celle que l’on a pas à faire — un inconnu
Dans mon actuelle mission, nous développons une application sous Symfony2 avec Doctrine comme ORM. Voici grossièrement à quoi ressemble l’architecture globale :
Comme vous le voyez, l’application est dédoublée sur deux serveurs distincts qui ont interdiction de se parler, en dehors de la base de données. Nous avons été confrontés à des problématiques de performances qui nous a contraint d’utiliser le cache de Doctrine.
Il faut savoir qu’il y a 3 types de cache pour Doctrine :
- Query Cache : transformation DQL -> SQL;
- Result Cache : résultat de la requête;
- Metadata Cache : annotation des entities.
Si on regarde la liste des drivers, on s’aperçoit qu’il n’est pas évident de mutualiser du cache entre plusieurs serveur (qui ne peuvent pas communiquer directement ensemble).
C’est là que Redis arrive ♥
En quelques mots, Redis (pour REmote DIctionary Server) est un SGBD clé-valeur qui s’inscrit dans la mouvance NoSQL. En plus d’être simple d’utilisation, sa performance qui ferait pâlir Usain Bolt. Cela est principalement dû au fait que tout est persisté dans le cache du serveur. Si vous doutez encore de cette dernière phrase, sachez que YouPorn, Stack Overflow, Github… l’utilisent ;-)
Voici un exemple de fonctionnement :
Installation
L’installation de Redis est plutôt simple et se fait en quelques lignes de commande :
Il va ensuite falloir installer les bundles qui vont bien :
Et on modifie le app/AppKernel.php:
Dans le config.yml :
Et voilà pour l’installation.
A ce stade, seuls les caches de metadata et de query sont opérationnels. Pour la mise en cache du
résultat, il faudra le faire manuellement sur chaque requête.
Mettre en cache le résultat
Terminé les requêtes inlines dans les controllers ! Vous allez désormais devoir utiliser le DQL ou le QueryBuilder.
Maintenant, si l’on recharge la page, cette requête ne se fera plus via MySQL mais bien via Redis.
On peut vérifier tout cela en allant sur Redis et en rentrant la commande suivante : KEYS *
. Voici
ce que l’on va avoir :
La première ligne va contenir les métadonnées de la classe Beer. La seconde, contiendra la requête
et son résultat. Si vous voulez avoir une clé un peu plus digeste, vous pouvez utiliser cette
méthode $query->setResultCacheId('my_wonderful_key');
ou même faire 1 pierre 3 coups :
$query->useResultCache(true, 3600, 'my_wonderful_key');
.
Voici ce que nous aurons : (je n’ai pas réussi à supprimer le [1]
)
Pour nettoyer le cache, voici quelques commandes :
Invalider le cache des requêtes
C’est bien de mettre en place un système de cache, mais vous n’allez pas demander à vos utilisateurs de lancer la commande après chaque opération. Il va donc falloir utiliser les événements de Doctrine, et plus particulièrement l’entity listeners Si vous faites des tests, pensez à commenter le cache des métadonnées ;-)
Définissez le service :
Ici, beer_all
est le nom de l’id assigné au cache.
Le service.yml :
Et enfin l’annotation sur l’entity :
Les plus pointilleux d’entre vous auront remarqués que je redéfini le TTL de ma clef au lieu de la
supprimer. En effet, si je supprime ma clef, la nouvelle clef créée sera [beers_all][2]
. Et le
compteur augmentera ainsi de suite…
Avec cette technique de fainéant, on garde la main sur le nom de la clef qui sera toujours
[beers_all][1]
.
Pour aller plus loin
- Tutoriel/Sandbox Redis : http://try.redis.io
- 2e niveau de cache de Doctrine (encore à l’état expérimental)
Tips
Utiliser les pipes unix avec le redis-cli : echo "KEYS *" | ./path/to/redis-cli | grep beer
Vérifier le TTL d’une clef: TTL [beer-id-42][1]
About the author
Hey, I'm Maxence Poutord, a passionate software engineer. In my day-to-day job, I'm working as a senior front-end engineer at Orderfox. When I'm not working, you can find me travelling the world or cooking.
Follow @_maxpou