6 leçons tirées de l'expérience d'optimisation des performances du service Node.js

Klarna a fait de gros efforts pour aider les développeurs à créer des services sécurisés et de haute qualité. L'un des outils destinés aux développeurs est une plate-forme pour effectuer des tests A / B. Le composant le plus important de ce système est la multitude de processus qui, pour chaque demande entrante, décident du type de test (A ou B) auquel envoyer la demande. Ceci, à son tour, détermine la couleur d'affichage du bouton, la disposition à afficher à l'utilisateur ou même le package tiers à utiliser. Ces décisions ont un impact direct sur l'expérience utilisateur.



Klarna utilise la plateforme Node.js. L'article, dont nous publions aujourd'hui la traduction, est consacré aux enseignements que les spécialistes de l'entreprise ont réussi à tirer de l'expérience de l'optimisation des performances de leur service.

Leçon numéro 1: les tests de performances peuvent donner l'assurance que la vitesse du système ne se dégrade pas à chaque version


Les performances de chaque processus jouent un rôle énorme, car ces processus sont utilisés de manière synchrone dans les chemins de décision critiques de l'écosystème Klarna. L'exigence de performance habituelle pour de telles tâches est que pour 99,9% des demandes, la décision doit être prise avec un retard, dont le temps est exprimé en un chiffre. Afin de s'assurer que le système ne s'écarte pas de ces exigences, la société a développé un convoyeur pour tester les contraintes du service.

Leçon numéro 2: «enrouler» indépendamment la charge, vous pouvez identifier les problèmes avant même qu'ils n'atteignent la production


Bien que nous n'ayons pratiquement pas constaté de problèmes de performances pendant les deux années d'utilisation de la plate-forme en production, des tests ont clairement indiqué certains problèmes. En quelques minutes du test, à un niveau de réception des demandes modérément stable, le temps de traitement de la demande a fortement augmenté, passant de valeurs normales à plusieurs secondes.


Informations sur le temps requis pour traiter la demande. Une sorte de problème détecté

Nous avons décidé, même si cela ne s'était pas encore produit en production, que ce n'était qu'une question de temps. Si la charge réelle atteint un certain niveau, nous pouvons rencontrer quelque chose de similaire. Par conséquent, il a été décidé d'étudier cette question.

Leçon numéro 3: les tests de résistance à long terme peuvent identifier une variété de problèmes. Si tout semble correct, essayez d'augmenter la durée du test.


Une autre chose à noter est que les problèmes de notre système apparaissent après 2-3 minutes de travail sous charge. Au début, nous avons effectué le test pendant seulement 2 minutes. Et le problème n'a été constaté que lorsque le temps d'exécution du test a été porté à 10 minutes.

Leçon numéro 4: n'oubliez pas de prendre en compte le temps nécessaire à la résolution DNS des noms, compte tenu des requêtes sortantes. N'ignorez pas la durée de vie des entrées de cache - cela peut sérieusement perturber l'application


Habituellement, nous surveillons les services en utilisant les mesures suivantes: nombre de demandes entrantes par seconde, durée de traitement des demandes entrantes, niveau d'erreurs. Cela nous donne de très bons indicateurs de l'état du système, indiquant s'il y a des problèmes.

Mais ces métriques ne fournissent pas d'informations précieuses lors du dysfonctionnement du service. En cas de problème, vous devez savoir où se trouve le goulot d'étranglement du système. Dans de tels cas, vous devez surveiller les ressources utilisées par le runtime Node.js. Il est évident que la composition des indicateurs, dont l'état est contrôlé dans les situations problématiques, inclut l'utilisation du processeur et de la mémoire. Mais parfois, la vitesse du système ne dépend pas d'eux. Dans notre cas, par exemple, le niveau d'utilisation du processeur était faible. On pourrait en dire autant du niveau de consommation de mémoire.

Une autre ressource qui détermine les performances des projets Node.js est la boucle d'événements. Tout comme il est important pour nous de connaître la quantité de mémoire utilisée par le processus, nous devons savoir combien de «tâches» vous avez besoin pour traiter la boucle d'événements. La boucle d'événements Node.js est implémentée dans la bibliothèque libuv C ++ ( voici une bonne vidéo à ce sujet). Les «tâches» sont appelées ici «demande active». Une autre mesure importante est le nombre de «descripteurs actifs» qui sont représentés par des descripteurs de fichiers ouverts ou des sockets utilisés par les processus Node.js. Une liste complète des types de descripteurs peut être trouvée dans la documentation libuv. Par conséquent, si le test utilise 30 connexions, on peut s'attendre à ce que le système ait 30 descripteurs actifs. L'indicateur caractérisant le nombre de requêtes actives indique le nombre d'opérations en attente d'un descripteur particulier. Quelles sont ces opérations? Par exemple, opérations de lecture / écriture. Une liste complète d'entre eux peut être trouvée ici .

Après avoir analysé les métriques de service, nous avons réalisé que quelque chose n'allait pas ici. Alors que le nombre de descripteurs actifs était ce à quoi nous nous attendions (dans ce test - environ 30), le nombre de demandes actives était disproportionnellement élevé - plusieurs dizaines de milliers.


Descripteurs actifs et demandes actives

Certes, nous ne savions pas encore quels types de demandes étaient dans la file d'attente. Après avoir divisé les requêtes actives en types, la situation s'est un peu éclaircie. À savoir, les requêtes UV_GETADDRINFO se sont UV_GETADDRINFO très visibles. Ils sont générés lorsque Node.js essaie de résoudre le nom DNS.

Pourquoi le système génère-t-il autant de demandes de résolution de noms DNS? Il s'est avéré que le client StatsD que nous utilisions essayait de résoudre le nom d'hôte pour chaque message sortant. Il est à noter que ce client offre la possibilité de mettre en cache les résultats des requêtes DNS, mais le TTL des enregistrements DNS correspondants n'est pas pris en compte ici. Les résultats sont mis en cache pour une durée indéterminée. Par conséquent, si l'enregistrement est mis à jour après que le client a déjà résolu le nom correspondant, il ne le saura jamais. Étant donné que l'équilibreur de charge StatsD peut être redéployé avec une adresse IP différente et que nous ne pouvons pas forcer le redémarrage du service afin de mettre à jour le cache DNS, cette approche, qui utilise la mise en cache pendant une durée illimitée, ne nous convenait pas.

La solution que nous avons trouvée était d'utiliser un moyen externe au client pour mettre en cache les requêtes DNS. Ceci est facile à faire en exécutant le «patch singe» du module DNS. Le résultat était désormais bien meilleur qu'auparavant.


Informations sur le temps requis pour traiter la demande. Résultat de l'utilisation d'un cache DNS externe

Leçon n ° 5: effectuer des opérations d'E / S en mode batch. De telles opérations, même asynchrones, sont de gros consommateurs de ressources.


Après avoir résolu le problème ci-dessus, nous avons activé certaines fonctionnalités du service qui ont été désactivées précédemment et l'avons testé à nouveau. En particulier, nous avons inclus un code qui envoie un message au sujet Kafka pour chaque demande entrante. Le test a, une fois de plus, révélé des pics significatifs dans les résultats des mesures du temps de réponse (nous parlons de secondes), observés sur de grands intervalles de temps.


Informations sur le temps requis pour traiter la demande. Le test a révélé une forte augmentation du temps nécessaire à la formation des réponses

Ces résultats indiquent un problème évident précisément dans la fonction que nous avons incluse avant le test. En particulier, nous sommes confrontés au fait que l'envoi de messages à Kafka prend trop de temps.


Informations sur le temps requis pour générer des messages pour Kafka

Nous avons décidé d'utiliser ici l'amélioration la plus simple: mettre les messages sortants dans la file d'attente en mémoire et transférer ces messages toutes les secondes en mode batch. En exécutant à nouveau le test, nous avons constaté une nette amélioration du temps nécessaire au service pour former une réponse.


Informations sur le temps requis pour traiter la demande. Améliorations après l'organisation du traitement des messages par lots

Leçon numéro 6: avant d'essayer d'apporter des améliorations au système, préparez des tests dont les résultats peuvent être fiables


Le travail décrit ci-dessus sur l'optimisation des performances d'un service n'aurait pas été possible sans un mécanisme de test qui vous permet d'obtenir des résultats reproductibles et uniformes. La première version de notre système de test n'a pas donné de résultats uniformes, nous ne pouvions donc pas nous y fier pour prendre des décisions importantes. Après avoir investi dans la création d'un système de test fiable, nous avons pu tester le projet dans différents modes, expérimenter des corrections. Le nouveau système de test, pour la plupart, nous a donné la certitude que les résultats des tests obtenus étaient quelque chose de réel, et non pas des chiffres venus de nulle part.

Disons quelques mots sur les outils spécifiques utilisés pour organiser les tests.

La charge a été générée par un outil interne qui a facilité l'exécution de Locust en mode distribué. En général, tout se résumait à l'exécution d'une seule commande, après quoi les générateurs de charge étaient lancés, le script de test leur était transféré et les résultats visualisés par le panneau de contrôle Grafana étaient collectés. Les résultats correspondants sont présentés dans le matériel sur des graphiques avec un fond sombre. Voici à quoi ressemble le système dans le test du point de vue du client.

Le service sous test fournit des informations de mesure dans Datalog. Cette information est présentée ici avec des graphiques avec un fond clair.

Chers lecteurs! Quels systèmes de test de service Node.js utilisez-vous?

Source: https://habr.com/ru/post/fr484616/


All Articles