Nous avons mis en place un cache Varnish devant l’application pour servir de reverse proxy. Toutes les requĂȘtes qui sont dĂ©jĂ  en cache dans Varnish sont directement servies par celui-ci. Cette opĂ©ration est un simple lookup effectuĂ© par le systĂšme Varnish. Ce dernier est hautement optimisĂ© pour la performance, ce qui rend le processus extrĂȘmement efficace. Ces requĂȘtes prennent seulement quelques millisecondes Ă  ĂȘtre servies.

MĂ©triques

D’aprĂšs le monitoring de Varnish, la frĂ©quence des requĂȘtes sur 24 heures ressemble Ă  ceci:

Nombre de requĂȘtes par minute dans la console d’administration Varnish. En journĂ©e, elles varient entre 40'000 et 100'000 requĂȘtes/minute, alors que la nuit, elles chutent Ă  un niveau beaucoup plus bas.

Ce screenshot a Ă©tĂ© pris lors d’une journĂ©e typique, sans Ă©vĂ©nement particulier. En journĂ©e, Varnish sert entre 40'000 et 100'000 requĂȘtes par minute, avec des pics encore plus Ă©levĂ©s. La nuit, l’activitĂ© diminue fortement.

Environ ⅔ des requĂȘtes sont des cache hits dans Varnish. Le taux de hit est particuliĂšrement Ă©levĂ© lorsque la charge est forte, ce qui allĂšge considĂ©rablement la charge sur l’application backend.

Hits & misses du cache par minute reportĂ©s par la console d’administration Varnish.

Cette activité entraßne également une consommation de bande passante importante:

La consommation de bande passante varie entre 5M et 40M / minute, avec des pics atteignant 100M.

Optimiser au maximum la mise en cache

Toutes les requĂȘtes Ă  M-API doivent ĂȘtre authentifiĂ©es. Pour atteindre un taux de cache hit Ă©levĂ©, nous utilisons le pattern "user context", qui permet de cacher les requĂȘtes par groupes d’utilisateurs ayant les mĂȘmes permissions, plutĂŽt qu’au niveau de chaque utilisateur·rice authentifié·e. Tu peux en lire plus sur ce pattern dans ce blogpost.

Nous avons aussi configurĂ© Varnish pour nettoyer les paramĂštres de requĂȘte et l’en-tĂȘte Accept-Language, afin d’augmenter encore plus le taux de cache hit.

Comme l’application sait quand les donnĂ©es sont modifiĂ©es, nous avons dĂ©fini une longue durĂ©e de vie du cache. Nous permettons aussi Ă  l’application d’invalider activement le cache si nĂ©cessaire. Nous utilisons le mĂ©canisme xkey de Varnish, qui permet de taguer toutes les rĂ©ponses et de les invalider par tag.

Beaucoup de rĂ©sultats de recherche sont des listes de produits, comme celles obtenues via une recherche en texte libre ou par navigation dans les catĂ©gories. Lorsque les donnĂ©es changent, l’ordre des rĂ©sultats de recherche peut aussi changer. Comme nous utilisons un systĂšme de pagination, xkey seul ne suffit pas. Une modification affecte non seulement la page oĂč le produit apparaissait auparavant, mais aussi toutes les pages suivantes. Invalider toutes les listes Ă  chaque modification de produit rendrait le cache inutile.

Pour rĂ©soudre ce problĂšme, nous utilisons Edge Side Includes (ESI). Les rĂ©ponses des listes contiennent uniquement des instructions ESI qui font rĂ©fĂ©rence aux produits Ă  inclure, mais pas les donnĂ©es elles-mĂȘmes. GrĂące Ă  cela, la gĂ©nĂ©ration des listes est trĂšs efficace, nous permettant d’utiliser une durĂ©e de cache courte pour elles.

Varnish interprĂšte l’ESI pour rĂ©cupĂ©rer chaque produit individuellement et assembler la rĂ©ponse pour le client. Les sous-requĂȘtes pour les produits sont mises en cache plus longtemps et taguĂ©es avec leur ID produit, ce qui permet leur invalidation ciblĂ©e. Cela garantit que mĂȘme si la liste est en cache, les produits affichĂ©s sont immĂ©diatement mis Ă  jour.

Nous avons Ă©galement configurĂ© le mode "grace", qui permet Ă  Varnish de servir du contenu obsolĂšte si l’application backend ne rĂ©pond pas. Dans la plupart des cas d’usage de M-API, une information lĂ©gĂšrement dĂ©passĂ©e est prĂ©fĂ©rable Ă  une absence de rĂ©ponse.

Application serveur

Tout ne peut pas ĂȘtre gĂ©rĂ© par la mise en cache HTTP. Pour les requĂȘtes qui ne peuvent pas ĂȘtre mises en cache ou dont la rĂ©ponse n’est pas encore en cache, le temps de rĂ©ponse du backend est critique. PHP, et en particulier le framework Symfony que nous utilisons, est optimisĂ© pour des temps de rĂ©ponse rapides.

PHP est conçu pour les applications stateless. Dans M-API, chaque requĂȘte est totalement indĂ©pendante, ce qui permet l’étalonnage horizontal facile. L’infrastructure cloud surveille automatiquement la charge de l’application et ajoute ou supprime des instances en fonction de la demande.

Pour optimiser les temps de rĂ©ponse, nous avons utilisĂ© un profiler pour identifier les goulots d’étranglement et nous assurer que chaque amĂ©lioration apportait un rĂ©el gain. Nous avons identifiĂ© des donnĂ©es que nous avons ensuite stockĂ©es dans un cache applicatif (Redis), ce qui nous a permis de rĂ©duire considĂ©rablement les requĂȘtes vers MySQL.

AprÚs cette amélioration, une part significative du temps de traitement était consacrée à la conversion des objets en JSON. Nous avons exploré plusieurs solutions pour accélérer ce processus, avec au final une solution beaucoup plus rapide. Tu peux en lire plus dans cet article de blog.

GrĂące Ă  ces optimisations, nous avons atteint des temps de rĂ©ponse d’environ 50 millisecondes pour les rĂ©ponses produits – mĂȘme lorsque chaque produit contient plusieurs centaines de KB Ă  plus d’un MB de donnĂ©es JSON.