Chez Achievers, après avoir développé un framework pour les tests de charge afin d'aider nos équipes d'ingénierie à comprendre les performances de notre plateforme quand elle est soumise à une charge imprévue, l'équipe a pu améliorer les performances générales de la plateforme, ce qui a permis un débit de trafic 4 fois plus important et une scalabilité plus efficace des clusters. Ce blog examine les objectifs définis à partir des résultats de la base de référence initiale et comment nous avons résolu les goulots d’étranglement au cours de la phase de test. Après avoir obtenu un bon rapport sur l'état actuel de la plateforme, il était temps de dépasser la base de référence.
Dépassement de la base de référence
Examinons rapidement les métriques que nous avons monitorées et les rapports générés.
- Débit : le nombre de demandes par secondes pouvant être traitées par le système.
- Erreurs : le nombre de demandes qui ont échoué au cours de la période de test.
- Latence : le temps nécessaire au traitement d'une demande à partir du moment où elle est envoyée.
- Scalabilité : la capacité de la plateforme à traiter des charges de plus en plus importantes sans dégradation significative des performances.
Après avoir mis en évidence les métriques ci-dessus, nous avons défini un objectif pour chacune d'elles. Le but était de mesurer si nous pouvions effectivement réussir à améliorer notre système.
Pour mesurer les résultats des tests et faire le suivi des attentes, nous avons créé des dashboards d'observabilité intégrés à New Relic. Les dashboards ont préparé l'équipe à rapidement identifier les goulots d’étranglement et les problèmes qui se produisent pendant la période de test.
Après avoir revu les dashboards, il était évident que les goulots d’étranglement bloquaient les performances de notre plateforme.
Optimisation du débit
Équilibrage de la charge avec Istio
gRPC garde les sessions TCP ouvertes le plus longtemps possible pour optimiser le débit et minimiser les frais, mais les sessions longues complexifient l'équilibrage de charge. Cela est particulièrement un problème dans les environnements Kubernetes ajustés automatiquement. Lorsque la charge augmente, de nouveaux pods sont ajoutés, mais le client reste connecté aux anciens pods gRPC, ce qui entraîne une distribution inégale de la charge.
Vous trouverez ci-dessous un bon exemple de trafic gRPC allant vers le déploiement, mais non non équilibré.
Vous pouvez voir que nous obtenons des demandes sporadiques pour chaque pod, avec un pod qui n'a aucun trafic. Istio nous aide à distribuer les demandes en partageant les informations de connexion entre les proxies Envoy. Les proxies Envoy partagent le nombre de demandes reçues et Istio peut déterminer quel est le pod le moins occupé qui peut mieux répondre à la demande.
La configuration de l'équilibrage de charge sur votre DestinationRule, pour qu'elle utilise LEAST_REQUEST au lieu du routage en tourniquet par défaut, a aidé à résoudre le problème d'un trafic déséquilibré.
Après le déploiement de la nouvelle configuration loadBalancer, les résultats montrent une distribution équitable des demandes entre les pods et une forte augmentation du débit sur tous les services.
Erreurs
Côté client
Tous les goulots d’étranglement se trouvent sur le serveur. Nous avons remarqué quelques goulots d’étranglement sur le client testant la charge qui a généré la charge, ce qui a résulté en un taux d’erreur élevé. Bien que nous ayons suffisamment d'UC/de mémoire pour exécuter nos tests, certaines limites ont été révélées sur notre réseau.
Les machines virtuelles Google Cloud sans adresse IP externe envoient le trafic sortant via un Cloud NAT. Lors de l'exécution d'un test de charge depuis un autre projet GCP, le trafic de sortie a envoyé un pic d'erreurs OUT_OF_RESOURCES pendant la période de test.
L'équipe a ajouté des adresses IP supplémentaires et a fait passer les « ports autorisés vers une adresse de destination » vers Google Cloud NAT. Vous pouvez voir dans l'image ci-dessous que nous touchons une limite horizontale avant de résoudre le problème à la fin du graphique.
Une solution à long terme consisterait à passer complètement à IPV6 pour supprimer les limites de connexion de sortie. Achievers prévoit d'être sur IPV6 pour tout notre réseau en 2024.
Latence
Configuration Istio
Étant donné que tout le trafic des clusters passe par le proxy Istio Envoy, nous pensions que nous aurions les moyens d'améliorer la latence. Au cours de l'enquête, nous n'avons pas vu de longues transactions en attente dans Envoy, mais nous pensions que c'était un bon moment pour voir si nous pouvions améliorer Istio en ajustant certains aspects de la configuration. La simultanéité Istio est le nombre de fils de workers exécutés sur le sidecar du proxy Envoy. Si elle n'est pas définie, la valeur par défaut est 2. Si elle est définie sur 0, tous les cores de la machine sont utilisés sur la base des demandes et des limites de l'UC ; ces valeurs sont utilisées pour les contrôler, les limites prévalant sur les demandes.
Résultats de la simultanéité :
4 workers => 4434,11/s + p(95)=114,67 ms
6 workers => 4230,42/s + p(95)=98,3 ms
2 workers => 4519,10/s + p(95)=100,75 ms
Dans notre cas, la simultanéité Istio ne semble avoir aucun impact sur les performances. Cet exercice nous a permis de voir si cette solution serait avantageuse pour nous même si au bout du compte il n'y a pas eu de gains significatifs.
Améliorations du code
Un code qui est partagé peut souvent introduire des problèmes sur tous les services. Dans le cas, par exemple, d'un bogue dans notre connecteur de base de données qui cause une latence légèrement plus importante des connexions de sorties.
En utilisant le tracing de New Relic, nous avons pu facilement repérer un modèle. Dans l'image ci-dessus, vous pouvez voir que les résultats de la latence étaient élevés au niveau des points de terminaison dont les appels de base de données étaient lents. Des verrous de bases de données étaient utilisés pour passer au schéma nécessaire avant d'exécuter la demande. Le maintien de ces verrous prend plus de temps. Le problème a été résolu en supprimant un appel de changement des schémas et en ajoutant à la place le nom du schéma à la requête.
Après l'optimisation de la bibliothèque, une amélioration globale du temps de réponse moyen pour le service a été constatée.
Un autre problème de code a été révélé sur un ancien point de terminaison qui existait dans notre monolithe. Les performances du point de terminaison étaient vraiment médiocres et exigeaient souvent jusqu'à 10 secondes avant de compléter une demande.
Après la mise à niveau du point de terminaison et l'optimisation des requêtes de base de données, nous avons réussi à le réduire à quelques millisecondes.
Mise en cache
De grandes améliorations au niveau de la mise en cache ont pu être implémentées en déplaçant des images vers le CDN pour améliorer les temps de réponse. Quelques goulots d’étranglement étaient situés au niveau des services qui dépendent lourdement du cache Redis. Bien que Redis ne devrait pas constituer une dépendance pour les services, il améliore nettement les performances pour les applications.
En examinant les graphiques de ressources dans New Relic, nous avons découvert que les ressources de Redis étaient épuisées dans deux ou trois namespaces critiques. Les deux lignes violettes dépassent la limite de Kubernetes, ce qui révèle l'épuisement de la mémoire et des erreurs de mémoire insuffisante (OOM).
Une rapide demande de tirage pour pousser les demandes Redis Kubernetes a rapidement résolu l'épuisement de la mémoire. Globalement, cet exercice était excellent pour tester l'impact sur les performances des services fortement mis en cache lorsque les ressources Redis arrivent à saturation.
Scalabilité
Les nœuds sont les machines virtuelles sur lesquelles un cluster Kubernetes exécute les workloads. Bien que les machines virtuelles puissent être éphémères et aller et venir, vous devez garantir les bonnes performances et la scalabilité de votre cluster. Une fois que vous êtes passé par des améliorations de base telles que l'ajustement de l'UC du nœud, la mémoire et le disque, vous devriez examiner quelques-unes des autres modifications apportées ci-dessous, qui garantissent que votre infrastructure évolue efficacement.
Limitations de l'adresse IP
Les clusters sont créés avec des plages IP et souvent, ces valeurs ne sont jamais modifiées. Au début du test, nous nous sommes vite rendu compte que notre cluster pouvait assez rapidement atteindre la limite du nombre de workers qu'il contenait. Kubernetes prend le réseau du pod et le répartit sur le réseau des workers, ce qui limite le nombre de workers dans vos nodepools. En fonction de la valeur max_pods_per_node, la création de nœuds est liée au nombre d'adresses dans la plage d'adresse du pod.
Par exemple, si vous définissez le nombre maximal de pods par défaut sur 110
et la plage d'adresse IP secondaire des pods sur /21
, Kubernetes attribue une plage CIDR /24
aux nœuds sur le cluster. Ceci autorise un maximum de 2(24-21) = 23 = 8
nœuds sur le cluster.
La limite des adresses IP peut résulter en une incapacité pour les nœuds du cluster d'évoluer et à l'étranglement des workloads ou pire, le plantage en raison de l'épuisement des ressources. Nous pouvons facilement résoudre ce problème, car nous avons simplement besoin d'ajuster certaines des plages IP attribuées aux pods et aux workers. Google a également présenté des « plages IP secondaires » qui peuvent aider les équipes qui doivent recréer leurs clusters pour résoudre ce problème.
TopologySpreadConstraints
Les déploiements Kubernetes fortement utilisés peuvent devoir être distribués entre différents workers pour empêcher l'épuisement des ressources. L'utilisation des pods topologySpreadConstraints est répartie entre les domaines d'échec des clusters tels que les régions, zones, hôtes et d'autres topologies définies par l'utilisateur.
Nous utilisons souvent cette fonctionnalité sur nos services critiques pour assurer la scalabilité efficace des clusters Kubernetes. Lorsque Unsatisfiable est défini sur DoNotSchedule, le cluster peut être obligé d'évoluer si plus de deux pods sont alloués à chaque worker.
Si nous laissons le cluster évoluer en fonction des services critiques, nous nous assurons que nous avons suffisamment d'UC et de mémoire pendant un pic de trafic. L'ajustement de cette valeur avec les valeurs de scalabilité moyennes a permis au cluster d'évoluer 10 fois plus efficacement.
Conclusion
Après avoir résolu les gros goulots d’étranglement, il était temps de revoir nos objectifs initiaux. Les résultats étaient prêts !
L'équipe a pu atteindre nos objectifs dans tous les cas et elle a même souvent dépassé nos attentes. Nous étions soulagés de voir que nous n'avions pas eu d'impact sur les SLO que nous avions définis pour les erreurs ou la latence à la fin de notre test initial. Nous avons multiplié par 4 le débit et cette amélioration nous a permis de traiter en toute confiance le trafic de basculement dans une seule région pendant une panne. Globalement, la scalabilité du cluster a également été améliorée en introduisant des ressources de tailles appropriées, parallèlement au traitement des demandes d'équilibrage de la charge gRPC par Istio.
Bien que des améliorations soient toujours possibles, la plateforme Achievers dispose maintenant d'un framework flexible permettant de continuellement tester la charge et mesurer les performances.
Découvrez plus de témoignages de clients
Cet article a été publié pour la première fois le 22 novembre 2023 sur Medium.
Les opinions exprimées sur ce blog sont celles de l'auteur et ne reflètent pas nécessairement celles de New Relic. Toutes les solutions proposées par l'auteur sont spécifiques à l'environnement et ne font pas partie des solutions commerciales ou du support proposés par New Relic. Veuillez nous rejoindre exclusivement sur l'Explorers Hub (discuss.newrelic.com) pour toute question et assistance concernant cet article de blog. Ce blog peut contenir des liens vers du contenu de sites tiers. En fournissant de tels liens, New Relic n'adopte, ne garantit, n'approuve ou n'approuve pas les informations, vues ou produits disponibles sur ces sites.