Site web - Performance et scalabilité

Pour qu'un site internet soit performant et scalable il y a quelques règles importantes à respecter, notamment sur la gestion des caches. Je vais essayer d'exposer quelques points ici.

Introduction
CDN
Reverse Proxy
Page Cache
Object Cache
DB Cache
API Cache
Cache - Durée de vie
Cache - Mise à jour
Exemple d'architecture
Conclusion


Introduction

Un site internet performant est un site capable de supporter un grand nombre de visiteurs sans pour autant effondrer le serveur sur lequel il est hébergé. Il est important également que ce site ne consomme pas inutilement des ressources par des appels répétés et inutiles à la base de données.

La scalabilité quand à elle est le fait qu'un site continu de fonctionner normalement malgré la hausse du nombre de visiteur. En effet, en fonction de certaines limitations (vitesse du SGBD, qualité des requêtes sur celui-ci, appels à une API externe, ...) un site peut rapidement voir ses performances se dégrader au fur et à mesure de la hausse du nombre de visiteur. Notamment quand ceux ci sont sur le site en même temps.
Cela peut également concerner la capacité de faire évoluer l'infrastructure en fonction de la demande. Mais je n'aborderai pas ce point ici.

Pour un site internet on va organiser son infra et son cache en plusieurs couches.
De la couche la plus "lointaine" de l'application, jusqu'à s'en rapprocher progressivement.
On ne mettra pas les mêmes informations en cache selon le niveau de distance par rapport au site.


CDN

Le CDN est composé de multiples serveurs répartis sur la planète. Il va stocker localement les informations qu'il aura récupéré du site internet.
Le fonctionnement le plus fréquent étant qu'au départ il ne dispose d'aucune information, quand un visiteur lui demandera un élément il ira le récupérer auprès du site web, puis il stockera une copie locale, et donnera la réponse au visiteur.

Toutefois, tout ne peut pas être stocké sur le CDN, et dans la plupart des cas on ne stockera que les éléments strictement public, comme les images, les fichiers css/js, etc.
Dans le cas de page publiques, qui sont 100% identiques pour tous les visiteurs on peut envisager de stocker certaines pages et sous certaines conditions sur le CDN. J'ai fais un article spécifique à Cloudflare sur ce sujet ici : Gestion du cache avec Cloudflare.


Reverse Proxy

Après le CDN on se rapproche de l'application pour arriver sur le reverse proxy.
Ici on va généralement utiliser des outils comme NGinx ou Varnish.
Ceux ci vont se comporter un peu comme le CDN, à la différence qu'ils sont sur le même serveur que le site, voir en "frontaux" sur des serveurs dans le même Data Center.

L'autre grande différence est qu'il permet un contrôle bien plus fin du cache et des conditions de mise en cache, comme la vérification du contenu d'un cookie par exemple. Toutefois pour des raisons de sécurité on ne stockera ici que des éléments publiques également. Un cookie pouvant être "fabriqué" par le visiteur.

Selon les besoins ou les possibilités techniques le CDN et le reverse proxy remplissent souvent le même rôle. Le CDN ayant l'avantage d'être réparti mondialement, là où le reverse proxy lui a l'avantage de permettre un contrôle + fin de la mise en cache.


Page Cache

Après le CDN et le reverse proxy on arrive réellement sur le serveur web.
Le page cache reste réservé a du contenu publique et sans restriction d'accès.

Ce cache va consister à sauvegarder une page web dynamique, construite en PHP par exemple, en page statique html. Généralement on va accompagner cela a une rewrite rule spécifique sur le serveur web.
Le serveur vérifie si la page en cache existe, et si oui, va la "servir" directement au visiteur sans passer par la partie PHP. Cela permet d'améliorer grandement les performances et la consommation de ressources côté serveur.


Object Cache

Jusqu'à présent je n'ai abordé que la question du cache pour le contenu public. C'est assez simple, on stock une version précalculée de la page web et on la "sert" directement au visiteur. Pas de contrôle d'accès, pas de contenu personnalisé.

Toutefois quand on a besoin d'un contenu personnalisé à présenter au visiteur il n'est plus possible de passer par ces solutions. Cela ne veut pas dire qu'il n'est pas possible de mettre des informations en cache. C'est là que vient l'object cache.

L'object cache consiste à mettre en cache, sous la forme de son choix, une partie d'une page web. Par exemple le menu, un article réservé aux abonnés, etc.

Pour ma part je préconise un simple "file cache" pour ce genre de contenu. C'est à dire que l'on stock le contenu pré calculé dans un fichier texte sur le serveur, à l'abri des regards indiscret, c'est à dire en dehors du "document root" dans la mesure du possible, ou en bloquant l'accès au dossier stockant ce cache.
Le volume de donnée pouvant être important je déconseille le stockage directement dans la mémoire, via Redis par exemple.


DB Cache

Une autre façon de stocker des éléments plus petits va être d'utiliser une forme de cache pour la base de données.
Concrètement, dans le cas d'une requête SQL à exécuter. On va créer un "hash" à partir de cette requête et s'en servir comme une clé pour son cache. Avant d'exécuter la requête on vérifie si cette clé existe dans le cache, si oui on va en récupérer le contenu sans aller interroger la base de donnée. Si la clé n'existe pas on exécute la requête normalement.

Pour le stockage de ce cache tout dépend du volume qu'il va représenter. Si la taille est maîtrisée on peut envisager de le stocker sur Redis, qui stock les éléments en mémoire. En cas de volume de données trop important je recommande de rester sur du file cache également.

Il est possible également de stocker les plus petites réponses qui sont appelés très fréquemment sur Redis. Comme la validation d'un token d'authentification (pour un temps limité), le nom du visiteur qui s'affiche sur toutes les pages, etc...


API Cache

L'API cache rejoint un peu la problématique du cache pour la base de données. Mais on interroge une API, souvent distante.

De base je déconseille vivement d'interroger une API distante en "live" pendant le passage d'un visiteur. L'idéal étant d'interroger cette API en mode "hors ligne" par un script qui tourne en arrière plan et afficher au visiteur le contenu en cache localement.

Ceci afin d'éviter que si l'API en question subit des ralentissements, voir est down, que cela n'entraîne l'indisponibilité de tout le site web, ou tout du moins son ralentissement.
Cela permet également de gérer les problématiques de "rate limite" en cas d'augmentation imprévue du nombre de visiteur sur votre site.

Concernant le mode de stockage de ce cache c'est la même problématique que l'object cache, tout va dépendre de la fréquence des appels à ce cache mais aussi de la taille. En cas de volume de données important cela peut fortement impacter la consommation mémoire si on choisit de tout stocker en RAM.


Cache - Durée de vie

Maintenant que l'on décidé de ce qui doit être mit en cache, il faut ensuite maintenir ce cache, s'assurer qu'il est toujours à jour et en assurer la sécurité.
Il y a plusieurs approche sur la façon de gérer son cache, et elle ne sera pas forcément identique selon le niveau de cache où l'on se trouve.

Au niveau du CDN ou du reverse proxy, surtout si l'application (le site) n'a pas de contrôle direct du cache on choisira de préférence un cache à durée de vie courte. Cela peut être du micro caching (2 à 5min), ou un peu plus long en fonction de la fréquence de mise à jour du site. Ce mode de fonctionnement vise essentiellement à limiter le nombre de connexions simultanées à la base de données par exemple.
Ce genre de cache n'a pas vocation à être piloté par le site et on choisit de le laisser expirer quand du contenu est mit à jour sur le site.

Au niveau du serveur, là où l'application peut directement contrôler le cache on pourra utiliser un cache avec une durée de vie plus longue. Par exemple dans le cas d'un site publiant des articles ceux ci n'ont pas vocation à être modifié régulièrement (voir jamais), ainsi conserver un cache de 24H, voir plusieurs jours ne pose pas de problème majeur.
A contrario, la validation d'une authentification ne sera pas stockée en cache plus de quelques minutes, juste de quoi limiter des appels trop fréquents au backend (API / SGBD).


Cache - Mise à jour

Vient ensuite la gestion de la mise à jour du cache.
En effet dans le cas du micro caching (quelques minutes) on ne se soucie pas de garder son cache à jour, on attends simplement qu'il expire. Toutefois dans le cas où l'on choisit de stocker son cache plus longtemps, il devient nécessaire de le maintenir à jour, notamment de gérer les modifications.

Généralement on accompagnera cela de toute façon par une durée de vie raisonnable de son cache, 24H est une bonne valeur. Toutefois sur un site avec de très nombreux articles, dont une grande partie son rarement consultés, il peut être pertinent d'avoir un cache plus long, jusqu'à 7 jours, voir 1 mois si on est sûr et certain de la gestion de son cache.


Exemple d'architecture

Pour essayer de rendre tout ceci un peu plus concret je vais prendre l'exemple d'un site de news.
Une partie des news sont publiques et accessibles à tous, alors qu'une autre partie est réservée aux abonnés.
A cela se rajoute un espace d'administration et pour les rédacteurs.
Quelle pourrait être la façon d'organiser son infra et coder le site pour cela ?

En appliquant ces principes généraux le site pourra supporter des milliers de visiteurs simultanément, notamment ceux qui ne sont pas authentifiés. La montée en activité n'impactera quasiment pas le bon fonctionnement du site et les coûts en infrastructures seront maîtrisés.
Bien entendu si l'on passe de 10 visiteurs simultanés à 10k abonnés cela va se ressentir sur les performances, mais si les appels au SGBD sont bien maîtrisés cela devrait être gérable du moment que le serveur est convenablement dimensionné.

Conclusion

Pour résumé de façon très simple :