Comme dans
Un jour, je me suis réveillé avec un e-mail mécontent en raison de longs retards avec Alvin, que nous prévoyions de lancer dans un avenir proche. Plus précisément, le client a connu une latence du 99e centile de l’ordre de 50 ms, bien au-dessus de notre budget de latence. C'était surprenant car j'ai testé le service de manière approfondie, notamment sur la latence, qui est une plainte courante.
Avant de tester Alvin, j'ai effectué de nombreuses expériences avec 40 10 requêtes par seconde (QPS), toutes montrant une latence inférieure à 40 ms. J'étais prêt à déclarer que je n'étais pas d'accord avec leurs résultats. Mais en regardant à nouveau la lettre, j'ai remarqué quelque chose de nouveau : je n'avais pas exactement testé les conditions mentionnées, leur QPS était bien inférieur au mien. J'ai testé à 1 XNUMX QPS, mais ils seulement à XNUMX XNUMX. J'ai mené une autre expérience, cette fois avec un QPS inférieur, juste pour les apaiser.
Depuis que je blogue à ce sujet, vous avez probablement déjà compris que leurs chiffres étaient exacts. J'ai testé mon client virtuel encore et encore, avec le même résultat : un faible nombre de requêtes augmente non seulement la latence, mais augmente le nombre de requêtes avec une latence supérieure à 10 ms. En d’autres termes, si à 40 50 QPS, environ 50 requêtes par seconde dépassaient 1 ms, alors à 100 50 QPS, il y avait XNUMX requêtes supérieures à XNUMX ms par seconde. Paradoxe!
Affiner la recherche
Face à un problème de latence dans un système distribué comportant de nombreux composants, la première étape consiste à créer une courte liste de suspects. Approfondissons un peu l'architecture d'Alvin :
Un bon point de départ est une liste des transitions d'E/S terminées (appels réseau/recherches de disque, etc.). Essayons de comprendre où se situe le retard. Outre les E/S évidentes avec le client, Alvin franchit une étape supplémentaire : il accède au magasin de données. Cependant, ce stockage fonctionne dans le même cluster qu'Alvin, la latence devrait donc y être moindre qu'avec le client. Ainsi, la liste des suspects :
- Appel réseau du client à Alvin.
- Appel réseau d'Alvin au magasin de données.
- Recherchez sur le disque dans le magasin de données.
- Appel réseau de l'entrepôt de données à Alvin.
- Appel réseau d'Alvin à un client.
Essayons de rayer quelques points.
Le stockage des données n'a rien à voir avec ça
La première chose que j'ai faite a été de convertir Alvin en un serveur ping-ping qui ne traite pas les requêtes. Lorsqu'il reçoit une requête, il renvoie une réponse vide. Si la latence diminue, alors un bug dans l’implémentation d’Alvin ou de l’entrepôt de données n’est pas inhabituel. Dans la première expérience, nous obtenons le graphique suivant :
Comme vous pouvez le constater, il n'y a aucune amélioration lors de l'utilisation du serveur ping-ping. Cela signifie que l’entrepôt de données n’augmente pas la latence et que la liste des suspects est réduite de moitié :
- Appel réseau du client à Alvin.
- Appel réseau d'Alvin à un client.
Super! La liste se réduit rapidement. Je pensais avoir presque compris la raison.
gRPC
Il est maintenant temps de vous présenter un nouveau joueur : gRPC
bien optimisé et largement utilisé, c'était la première fois que je l'utilisais sur un système de cette taille et je m'attendais à ce que mon implémentation soit pour le moins sous-optimale.
disponibilité gRPC
dans la pile a soulevé une nouvelle question : c'est peut-être mon implémentation ou moi-même gRPC
causant un problème de latence ? Ajout d'un nouveau suspect à la liste :
- Le client appelle la bibliothèque
gRPC
- bibliothèque
gRPC
effectue un appel réseau à la bibliothèque sur le clientgRPC
sur le serveur - bibliothèque
gRPC
contacte Alvin (pas d'opération en cas de serveur ping-pong)
Pour vous donner une idée de ce à quoi ressemble le code, mon implémentation client/Alvin n'est pas très différente de celle client-serveur
Remarque : La liste ci-dessus est un peu simplifiée car
gRPC
permet d'utiliser votre propre modèle de thread (modèle ?), dans lequel la pile d'exécution est entrelacéegRPC
et la mise en œuvre par les utilisateurs. Par souci de simplicité, nous nous en tiendrons à ce modèle.
Le profilage va tout régler
Après avoir barré les magasins de données, je pensais avoir presque terminé : « Maintenant, c'est facile ! Appliquons le profil et découvrons où se produit le retard. je
J'ai pris quatre profils : avec un QPS élevé (faible latence) et avec un serveur ping-pong avec un faible QPS (latence élevée), aussi bien côté client que côté serveur. Et juste au cas où, j'ai également pris un exemple de profil de processeur. Lorsque je compare des profils, je recherche généralement une pile d’appels anormale. Par exemple, du mauvais côté, avec une latence élevée, il y a beaucoup plus de changements de contexte (10 fois ou plus). Mais dans mon cas, le nombre de changements de contexte était presque le même. À ma grande horreur, il n’y avait rien d’important là-bas.
Débogage supplémentaire
J'étais désespéré. Je ne savais pas quels autres outils je pouvais utiliser, et mon plan suivant consistait essentiellement à répéter les expériences avec différentes variantes plutôt que de diagnostiquer clairement le problème.
Et qu'est-ce qui se passerait si
Dès le début, j'étais préoccupé par la latence spécifique de 50 ms. C'est un très grand moment. J'ai décidé de supprimer des morceaux du code jusqu'à ce que je puisse déterminer exactement quelle partie était à l'origine de cette erreur. Puis vint une expérience qui a fonctionné.
Comme d’habitude, avec le recul, il semble que tout était évident. J'ai placé le client sur la même machine qu'Alvin - et j'ai envoyé une demande à localhost
. Et l’augmentation de la latence a disparu !
Quelque chose n'allait pas avec le réseau.
Acquérir les compétences d’un ingénieur réseau
Je dois l'admettre : ma connaissance des technologies de réseau est terrible, d'autant plus que je travaille avec elles quotidiennement. Mais le réseau était le principal suspect et je devais apprendre à le déboguer.
Heureusement, Internet aime ceux qui veulent apprendre. La combinaison de ping et tracert semblait être un bon début pour déboguer les problèmes de transport réseau.
Tout d'abord, j'ai lancé
Puis j'ai essayé
Ce n'était donc pas mon code, l'implémentation de gRPC ou le réseau qui était à l'origine du retard. Je commençais à craindre de ne jamais comprendre cela.
Maintenant, sur quel système d'exploitation sommes-nous
gRPC
largement utilisé sous Linux, mais exotique sous Windows. J'ai décidé de tenter une expérience qui a fonctionné : j'ai créé une machine virtuelle Linux, compilé Alvin pour Linux et l'ai déployé.
Et voici ce qui s'est passé : le serveur de ping-pong Linux n'a pas eu les mêmes délais qu'un hôte Windows similaire, même si la source de données n'était pas différente. Il s'avère que le problème réside dans l'implémentation de gRPC pour Windows.
L'algorithme de Nagle
Tout ce temps, je pensais qu'il me manquait un drapeau gRPC
. Maintenant je comprends ce que c'est vraiment gRPC
Le drapeau Windows est manquant. J'ai trouvé une bibliothèque RPC interne qui, j'étais sûr, fonctionnerait bien pour tous les indicateurs définis
Presque Terminé : j'ai commencé à supprimer les indicateurs ajoutés un par un jusqu'à ce que la régression revienne afin de pouvoir en identifier la cause. C'était infâme
gRPC
cet indicateur a été défini dans l'implémentation Linux pour les sockets TCP, mais pas dans Windows. je suis ce
Conclusion
La latence plus élevée à faible QPS était due à l'optimisation du système d'exploitation. Rétrospectivement, le profilage n'a pas détecté de latence car il a été effectué en mode noyau plutôt qu'en mode noyau.
Quant à l'expérience localhost, elle n'a probablement pas touché au code réseau réel et l'algorithme de Nagle n'a pas fonctionné, donc les problèmes de latence ont disparu lorsque le client a atteint Alvin via localhost.
La prochaine fois que vous constaterez une augmentation de la latence à mesure que le nombre de requêtes par seconde diminue, l'algorithme de Nagle devrait figurer sur votre liste de suspects !
Source: habr.com