Comment accélérer rapidement et facilement l'accès aux applications API?

La réponse est simple: utiliser des outils éprouvés comme la mise en cache et la mise à l'échelle horizontale. Nous devons dire tout de suite que ce ne sont pas les seuls outils, mais le plus souvent, ce sont les approches classiques éprouvées qui s'avèrent être les plus efficaces même dans les conditions modernes. Regardons un exemple pratique.



À propos du problème d'origine



La plateforme vidéo PREMIER, comme il sied à une ressource moderne, a créé un service de recommandation pour ses clients basé sur l'apprentissage automatique. De nombreux utilisateurs se tournent vers la plateforme vidéo - environ un million par jour, PREMIER est très populaire - et les appels passent à la fois par un formulaire Web, depuis des applications pour appareils mobiles et depuis Smart TV.



Les données initiales sur la base desquelles le machine learning de notre service fonctionne sont stockées dans le SGBD ClickHouse en colonne. Selon le calendrier, les données sont traitées en arrière-plan pour construire des modèles (qui seront utilisés pour émettre des recommandations finales). Les résultats des calculs sont enregistrés dans le SGBD relationnel PostgreSQL.



La solution au problème de l'interaction rapide entre le serveur d'application et le client en peu de temps reste toujours pertinente. Pour garantir la vitesse de travail requise - et le temps de réponse ne devrait pas dépasser 50 ms - nous avons dû optimiser la structure de la base de données relationnelle, implémenter la mise en cache et la mise à l'échelle horizontale. Nous allons parler de certaines des techniques maintenant.







À propos de la mise en cache



La mise en cache est une technique d'optimisation courante. Nous créons un stockage intermédiaire supplémentaire, plus rapide que le stockage principal, et y plaçons les données les plus demandées. L'accès aux données mises en cache est considérablement accéléré, ce qui augmente considérablement la vitesse de l'application. Lors du développement d'une application à forte charge, la mise en cache est l'option la plus courante pour optimiser les performances des applications sans augmenter les ressources matérielles. La mise en cache peut entraîner des économies en termes d'utilisation des ressources pour régénérer la même sortie pour la même entrée.



La capacité du cache est limitée - plus le stockage est rapide, plus il est cher - il doit donc être utilisé efficacement. Bien sûr, vous pouvez théoriquement vous passer de la mise en cache si votre stockage principal est suffisamment rapide. Mais ce sera économiquement non rentable, car vous devrez effectuer une mise à niveau matérielle importante, se résumant souvent à une augmentation de la RAM et / ou au remplacement des disques du HDD au SSD. Ceux. augmenter considérablement les besoins en infrastructure, ce qui affectera les paramètres économiques de l'ensemble de l'application en cours de création. Sans une mise en cache éprouvée, dans la plupart des cas, il sera difficilement possible de créer un produit de masse.



Cependant, l'ajout d'une couche de mise en cache ne résout pas automatiquement tous les problèmes. Il faut également réfléchir aux règles de son remplissage, qui dépendent des caractéristiques de la tâche, qui peuvent évoluer en fonction de la situation et au fur et à mesure de l'évolution du service. Et gardez à l'esprit que ce n'est pas une panacée, mais seulement un remède qui soulagera les symptômes de problèmes de performances dans des parties spécifiques de votre application. Si votre application a des problèmes architecturaux profonds, un environnement d'exécution médiocre, la mise en cache est plus susceptible d'ajouter à vos problèmes.



Il existe plusieurs options pour stocker les ressources mises en cache: localement - sur l'instance client dans le navigateur, dans un service CDN tiers, côté application. Nous parlerons de la mise en cache dans l'application. La mémoire de processus d'application est probablement l'option de mise en cache de données la plus courante que vous rencontrerez. Cependant, cette solution a ses inconvénients, car la mémoire est associée à un processus exécutant une tâche spécifique. Ceci est d'autant plus important si vous envisagez de faire évoluer l'application, car la mémoire n'est pas allouée entre les processus, c'est-à-dire il ne sera pas disponible dans d'autres processus, tels que ceux responsables du traitement asynchrone. Oui, la mise en cache fonctionne, mais nous n'en tirons vraiment pas pleinement profit.



Mise en cache des recommandations







Revenant au projet, nous comprenons la nécessité d'une solution de mise en cache centralisée. Pour un cache partagé, vous pouvez utiliser, par exemple, Memcached: si une application est connectée à la même instance, vous pouvez l'utiliser dans de nombreux processus. D'une part, Memcached est une solution simple et pratique, d'autre part, elle est plutôt limitée en ce qui concerne la gestion précise de l'invalidation, du typage des données et des requêtes plus complexes vers le magasin de données mis en cache. Maintenant, en fait, le stockage Redis est devenu la norme dans les tâches de mise en cache, ce qui est dépourvu des inconvénients de Memcached.



Redis est un stockage rapide de valeurs clés. Cela augmente l'efficacité du travail avec les données, car il devient possible de définir la structure. Fournit un contrôle granulaire sur l'invalidité et la préemption, vous permettant de choisir parmi six politiques différentes. Redis prend en charge à la fois la préemption paresseuse et impatiente, ainsi que la préemption temporelle. L'utilisation des structures de données Redis peut fournir des optimisations tangibles, selon les entités commerciales. Par exemple, au lieu de stocker des objets sous forme de chaînes sérialisées, les développeurs peuvent utiliser une structure de données de hachage pour stocker et manipuler des champs et des valeurs par clé. Le hachage élimine le besoin de récupérer la chaîne entière, de la désérialiser et de la remplacer dans le cache par une nouvelle valeur à chaque mise à jour, ce qui signifie moins de consommation de ressources et de meilleures performances. Autres structures de données,Les «feuilles», «ensembles», «ensembles triés», «hyperlogs», «bitmaps» et «géo-index» suggérés par Redis peuvent être utilisés pour mettre en œuvre des scénarios encore plus complexes. Les ensembles triés pour l'analyse des données de séries chronologiques offrent une complexité et un volume réduits lors du traitement et du transfert des données. La structure de données HyperLogLog peut être utilisée pour compter les éléments uniques dans un ensemble en utilisant uniquement une petite quantité de mémoire persistante, en particulier 12 Ko pour chaque HyperLogLog (plus quelques octets pour la clé elle-même). Une partie importante des quelque 200 commandes disponibles dans Redis est consacrée aux opérations de traitement des données et à l'intégration de la logique dans la base de données elle-même à l'aide de scripts Lua.Les commandes et les capacités de script intégrées offrent une flexibilité pour traiter les données directement dans Redis sans avoir à envoyer des données sur le réseau à votre application, ce qui réduit la surcharge de mise en œuvre d'une logique de mise en cache supplémentaire. Le fait de disposer des données du cache immédiatement après un redémarrage peut réduire considérablement le temps de préchauffage du cache et alléger la charge de recalculer le contenu du cache à partir du magasin de données principal. Nous parlerons des fonctionnalités de la configuration Redis et des perspectives de clustering dans les articles suivants.Nous parlerons des fonctionnalités de la configuration Redis et des perspectives de clustering dans les articles suivants.Nous parlerons des fonctionnalités de la configuration Redis et des perspectives de clustering dans les articles suivants.



Après avoir choisi un outil de mise en cache, le principal problème était la synchronisation des données stockées dans le cache et des données stockées dans l'application. Il existe différentes manières de synchroniser les données en fonction de la logique métier de votre application. Dans notre cas, la difficulté résidait dans la création d'un algorithme d'invalidation des données. Les données placées dans le cache y sont stockées pendant un temps limité, tant qu'il y a un besoin de les utiliser dans la situation actuelle, ou du moins la probabilité d'une telle. Au fur et à mesure que la situation évolue, ils doivent faire de la place pour d'autres données qui, dans les conditions modifiées, sont plus nécessaires. La tâche principale dans ce cas est la sélection des critères par lesquels les données seront expulsées du cache. Le plus souvent, c'est le moment de la pertinence des données, cependant, il convient de se rappeler d'autres paramètres: à propos du volume, du classement (à condition égale, avec des tolérances,durée de vie), catégorie (données principales ou auxiliaires), etc.



L'obsolescence du temps est le moyen de base et courant de maintenir les données à jour. Cette méthode, compte tenu de la mise à jour centralisée périodique des données d'application de recommandation, est également utilisée par nous. Cependant, tout n'est pas aussi simple qu'il n'y paraît à première vue: dans ce cas, il est extrêmement important de surveiller le classement afin que seules les données vraiment populaires pénètrent dans le cache. Cela devient possible grâce à la collecte de statistiques de requêtes et à la mise en œuvre du "préchauffage des données", c'est-à-dire préchargement des données dans le cache au démarrage de l'application. La gestion de la taille du cache est également un aspect important de la mise en cache. Notre application génère environ des millions de recommandations, il n'est donc pas réaliste de stocker toutes ces données dans le cache. La gestion de la taille du cache se fait en supprimant les données du cache pour faire de la place pour de nouvelles données.Il existe plusieurs méthodes standard: TTL, FIFO, LIFO, dernier accès. Jusqu'à présent, nous utilisons TTL, car L'instance Redis ne va pas au-delà de la mémoire allouée et des ressources disque.



Rappelez-vous que le cache est différent. Le plus souvent, il existe deux catégories: écriture directe et différée. Dans le premier cas, l'écriture est effectuée de manière synchrone à la fois vers le cache et le stockage principal. Dans le second, dans un premier temps, l'écriture est effectuée uniquement dans le cache, et l'écriture dans le stockage principal sera différée jusqu'à ce que les données modifiées soient remplacées par un autre bloc de cache. Le cache de réécriture est plus difficile à mettre en œuvre, car il nécessite de surveiller les données dans le cache pour une écriture ultérieure dans le stockage principal lorsqu'il est supprimé du cache. Nous utilisons la première option en conjonction avec la procédure de préchauffage du cache. Notez que «l'échauffement» en soi est une tâche importante et difficile, notre solution sera discutée dans les articles suivants.



Évoluez dans une application de recommandation



Pour fournir un accès PREMIER à l'application de recommandation, nous utilisons le protocole HTTP, qui est souvent la principale option d'interaction avec l'application. Il convient pour organiser l'interaction entre les applications, en particulier si Kubernetes et Ingress Controller sont utilisés comme environnement d'infrastructure. L'utilisation de Kubernetes facilite la mise à l'échelle. L'outil est capable d'équilibrer automatiquement la demande entre les pods du cluster pour un fonctionnement uniforme, ce qui facilite la mise à l'échelle des développeurs. Le module Ingress Controller en est responsable, qui définit les règles de connexion externe aux applications dans Kubernetes. Par défaut, les applications dans Kubernetes ne sont pas accessibles depuis le réseau externe. Pour fournir un accès externe aux applications, vous devez déclarer une ressource Ingress qui prend en charge l'équilibrage automatique.Nous utilisons Nginx Ingress Controller qui prend en charge SSL / TLS, les règles de réécriture d'URI et VirtualServer et VirtualServerRoute pour acheminer les demandes vers différentes applications en fonction de l'URI et de l'en-tête d'hôte.



La configuration de base dans le contrôleur d'entrée vous permet d'utiliser uniquement les fonctions de base de Nginx - il s'agit d'un routage basé sur l'hôte et le chemin, et des fonctions supplémentaires telles que les règles de réécriture d'URI, les en-têtes de réponse supplémentaires, les délais de connexion ne sont pas disponibles. Les annotations appliquées à la ressource Ingress vous permettent d'utiliser les fonctions de Nginx lui-même (généralement disponibles via la configuration de l'application elle-même) et de modifier le comportement de Nginx pour chaque ressource Ingress.



Nous prévoyons d'utiliser le Nginx Ingress Controller non seulement dans le projet à l'étude, mais également dans un certain nombre d'autres applications, dont nous parlerons plus tard. Nous en parlerons dans les articles suivants.







Risques et conséquences de l'utilisation de la mise en cache



Toute équipe travaillant à l'optimisation d'une application gourmande en données se posera de nombreuses questions sur la façon d'optimiser la mise en cache. Dans le même temps, le problème de la mise en cache ne peut pas être résolu «une fois pour toutes»: avec le temps, divers problèmes se posent. Par exemple, à mesure que votre base d'utilisateurs augmente, vous pouvez rencontrer des statistiques contradictoires sur la popularité des données par catégorie, et ce problème devra être résolu.



Si la mise en cache est un outil puissant pour accélérer une application, ce n'est pas la seule solution. Selon votre situation, vous devrez peut-être optimiser la logique de l'application, modifier l'environnement de pile et d'infrastructure. Vous devez également être prudent lors de la validation des exigences non fonctionnelles. Il est possible qu'après discussion avec le propriétaire du produit, les exigences de l'application soient exagérées. Il faut se rappeler que chacune des solutions a ses propres caractéristiques.



Les risques liés à la fourniture de données obsolètes, à l'augmentation de la complexité globale de la solution et à la probabilité d'introduire des bogues latents doivent être pris en compte avant d'appliquer une méthode de mise en cache à un projet. Après tout, dans ce cas, la mise en cache ne fera que compliquer la résolution des problèmes, mais masquera simplement les problèmes de performances et d'évolutivité: les requêtes de base de données sont-elles lentes? - le cache permet un stockage rapide! Les appels API sont-ils lents? - cache les résultats sur le client! En effet, la complexité du code qui gère la mise en cache augmente considérablement à mesure que la complexité de la logique métier augmente.



Dans les premières versions de l'application, la mise en cache a vraiment un effet tangible immédiatement après la mise en œuvre. Après tout, la logique métier est également simple: vous enregistrez la valeur et la récupérez. L'invalidation est facile car les dépendances entre les entités commerciales sont triviales ou inexistantes. Cependant, au fil du temps, pour améliorer les performances, vous devrez mettre en cache un nombre croissant d'entités commerciales.



La mise en cache n'est pas une solution miracle pour les problèmes de performances. Dans de nombreux cas, l'optimisation de votre code et de votre stockage principal vous fera du bien à long terme. De plus, l'introduction de la mise en cache devrait être une réaction au problème et non une optimisation prématurée.



En conclusion, optimiser les performances des applications et les rendre évolutives est un processus continu qui vise à obtenir un comportement prévisible dans le cadre d'exigences non fonctionnelles spécifiées. La mise en cache est nécessaire pour réduire le coût du matériel et le temps de développement consacrés à l'amélioration des performances et de l'évolutivité.



Liens:






All Articles