Le cache c'est compliqué... ou pas!
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Pour ce qui est du nommage, je n'ai pas encore trouvé de solution miracle, mais pour le cache j'ai de bonnes pistes!
Pourquoi l'invalidation de cache est complexe
Le cache permet de rapprocher les données du service qui va les utiliser. Le but est d'éviter de longs échanges avec la base de données, surtout pour des données peu volatiles.
Le problème vient du changement d'état des données. Comment et à quel moment doit on recharger le cache? Si l'on souhaite limiter les accès à la base de données, on ne va pas déporter ces accès en asynchrone pour le cache.
Sur un monolithe mono-serveur, ce problème peut rapidement être résolu via l'utilisation d'une zone de mémoire partagée que les accès en écriture viennent mettre à jour au besoin. Mais à l'ère des micro-services et de la scalabilité, il est impossible d'utiliser cette solution!
Il faut dont un moyen de communication cohérent entre les systèmes afin de garantir que les données du cache soient bien à jour.
CQRS, Event-Driven?
Dans une architecture construite dans le respect des patterns Event-Driven et/ou CQRS, la mise à jour du cache ne posera que peu de problème. Que ce soit par souscription à un topic ou par création de messages dédiés, le cache pourra facilement se tenir à jour. Pour autant, il faudra garantir le suivi de ces principes dans le temps sur toutes les actions menant à un changement d'état des données.
Maintenir une architecture et des bonnes pratiques est toujours un but recherché. Mais si nous pouvions l'atteindre à chaque fois, les conversations autour de la dette technique n'aurait pas lieu! La pression des enjeux économiques, des changements sur le marché, de la compétition, des utilisateurs, etc. font qu'il y aura toujours des besoins à couvrir dans l'urgence. L'urgence et la pression vont faire augmenter cette dette technique fortement.
Dans notre monde moderne qui va toujours plus vite, les changements doivent pouvoir s'opérer le plus vite possible. Les effets de chaque changement sur une application ne sont pas les simples à appréhender si l'0n ne prend pas le temps de l'analyse. C'est encore pire au niveau SI!
Dès lors que l'application aura besoin d'évolutions, des risques sur le maintien en cohérence de l'architecture globale apparaissent. De ce fait, la gestion du cache peut devenir un problème important provoquant l'effet inverse de celui recherché: l'insatisfaction des utilisateurs. Un utilisateur qui perdra du temps à voir son historique de commande, ou qui aura des commandes manquantes, ou qui ne pourra pas avoir un stock actualisé sur le site, etc.
Si une approche par les patterns peut permettre de mieux gérer les caches applicatifs, il y a toujours un risque dans le temps sur le suivi des patterns ainsi que leur usage. Ce risque peut avoir un impact fort sur les métiers et donc sur le business.
Approche CDC
CDC, ou Change(feed) Data Capture, est une fonctionnalité permettant, comme son nom l'indique, de capturer les changements opérés sur des données afin de les transmettre ailleurs. On peut voir le CDC comme une implémentation dédiée aux stockage de données des approches Event-Driven.
Dans cette logique, la base de données reste le point central de stockage et de garantie de la données. Cette garantie n'est pas à considérer comme acquise! Les systèmes NoSQL n'apporte que peu, voire pas du tout, de cohérence et de garantie sur les données. Encore moins lorsque l'on souhaite modifier des données liées dans une seule transaction. Mais il ne faut pas pointer du doigt ces solutions, car la grande majorité des solution relationnelles ont mis en place des niveau d'isolation plus faible permettant d'améliorer les performances globales au détriment de la qualité et de la cohérence des données.
Dans le cadre de transaction Serializable, on offre une garantie complète sur les cohérence des données dès lors qu'un commit a été acquitté par le système. Dans ce contexte, si la base peut émettre cet état, ou ce changement d'état pour être plus précis, directement vers le cache, nous pourrions garantir que le cache est cohérent.
Pour réaliser ce genre de d'implémentation, il faut donc
- Une base de données en mode Serializable
- Une fonctionnalité pour émettre les changements
- Un cache ou un proxy acceptant les appels
Cockroach va permettre une utilisation avancée de la mécanique CDC (encore plus avec la nouvelle release 23.1 en approche à l'heure ou j'écris ce post) afin d'avoir des messages clairs que l'on soit sur de la création, mise à jour, ou suppression et donc de savoir comment réagir au niveau du cache.
Cockroach n'offre qu'un seul et unique niveau d'isolation des transactions qui est Serializable. Il n'est pas possible de passer sur un Read Commited ou autre comme le propose les autres systèmes. De ce fait, la cohérence est garantie quoi qu'il advienne dans la base de données, et la fonctionnalité CDC ne pourra émettre que les changements validés globalement.
Un dernier mot
Je recommande fortement l'utilisation d'un proxy permettant de parser les messages venant de Cockroach et donc d'intervenir sur le cache. De cette manière, le cache et la base de données resteront découplés et pourront évoluer sur des rythmes et contraintes différentes facilement.
Je pense que ce système permet de faciliter la gestion du cache et son maintient en cohérence avec les données de manière plus simple et efficace. Il reste à utiliser le bon système de cache, avec les bonnes implémentations pour les appels et garantir que le use-case permet ce genre de fonctionnement!