SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

L'analyse et le réglage des performances constituent un outil puissant permettant de vérifier la conformité des performances pour les clients.

L'analyse des performances peut être utilisée pour vérifier les goulots d'étranglement dans un programme en appliquant une approche scientifique pour tester des expériences de réglage. Cet article définit une approche générale de l'analyse et du réglage des performances, en utilisant un serveur Web Go comme exemple.

Go est particulièrement efficace ici car il dispose d'outils de profilage pprof dans la bibliothèque standard.

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

stratégie

Créons une liste récapitulative pour notre analyse structurelle. Nous essaierons d’utiliser certaines données pour prendre des décisions au lieu d’apporter des changements basés sur l’intuition ou des conjectures. Pour ce faire, nous ferons ceci :

  • Nous déterminons les limites d'optimisation (exigences) ;
  • Nous calculons la charge de transaction pour le système ;
  • Nous effectuons le test (créons des données) ;
  • Nous observons;
  • Nous analysons : toutes les exigences sont-elles remplies ?
  • Nous l'établissons scientifiquement, faisons une hypothèse ;
  • Nous effectuons une expérience pour tester cette hypothèse.

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Architecture de serveur HTTP simple

Pour cet article, nous utiliserons un petit serveur HTTP en Golang. Tout le code de cet article peut être trouvé ici.

L'application analysée est un serveur HTTP qui interroge Postgresql pour chaque requête. De plus, il existe Prometheus, node_exporter et Grafana pour collecter et afficher les métriques des applications et du système.

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Pour simplifier, nous considérons que pour une mise à l'échelle horizontale (et une simplification des calculs), chaque service et base de données sont déployés ensemble :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Définir des objectifs

A cette étape, nous décidons de l'objectif. Que cherche-t-on à analyser ? Comment savoir quand il est temps de terminer ? Dans cet article, nous imaginerons que nous avons des clients et que notre service traitera 10 000 requêtes par seconde.

В Livre Google SRE Les méthodes de sélection et de modélisation sont discutées en détail. Faisons de même et construisons des modèles :

  • Latence : 99 % des requêtes doivent être complétées en moins de 60 ms ;
  • Coût : Le service doit consommer le montant minimum d’argent que nous pensons raisonnablement possible. Pour ce faire, nous maximisons le débit ;
  • Planification de la capacité : nécessite de comprendre et de documenter le nombre d'instances de l'application qui devront être exécutées, y compris la fonctionnalité de mise à l'échelle globale, ainsi que le nombre d'instances nécessaires pour répondre aux exigences initiales de charge et de provisionnement. redondance n+1.

La latence peut nécessiter une optimisation en plus de l'analyse, mais le débit doit clairement être analysé. Lors de l'utilisation du processus SRE SLO, la demande de délai émane du client ou de l'entreprise, représenté par le Product Owner. Et notre service remplira cette obligation dès le début, sans aucun réglage !

Mise en place d'un environnement de test

Avec l'aide d'un environnement de test, nous pourrons placer une charge mesurée sur notre système. Pour l'analyse, des données sur les performances du service Web seront générées.

Charge de transaction

Cet environnement utilise Végéter pour créer un taux de requêtes HTTP personnalisé jusqu'à son arrêt :

$ make load-test LOAD_TEST_RATE=50
echo "POST http://localhost:8080" | vegeta attack -body tests/fixtures/age_no_match.json -rate=50 -duration=0 | tee results.bin | vegeta report

Surveillance

La charge transactionnelle sera appliquée au moment de l'exécution. En plus des mesures de l'application (nombre de requêtes, latence de réponse) et du système d'exploitation (mémoire, CPU, IOPS), le profilage de l'application sera exécuté pour comprendre où elle rencontre des problèmes et comment le temps CPU est consommé.

Profilage

Le profilage est un type de mesure qui vous permet de voir où va le temps CPU lorsqu'une application est en cours d'exécution. Il vous permet de déterminer exactement où et combien de temps processeur est passé :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Ces données peuvent être utilisées lors de l'analyse pour obtenir un aperçu du temps CPU perdu et du travail inutile effectué. Go (pprof) peut générer des profils et les visualiser sous forme de graphiques de flamme à l'aide d'un ensemble d'outils standard. Je parlerai de leur guide d'utilisation et de configuration plus loin dans l'article.

Exécution, observation, analyse.

Faisons une expérience. Nous jouerons, observerons et analyserons jusqu’à ce que nous soyons satisfaits de la performance. Choisissons une valeur de charge arbitrairement faible pour l'appliquer afin d'obtenir les résultats des premières observations. À chaque étape suivante, nous augmenterons la charge avec un certain facteur d'échelle, choisi avec quelques variations. Chaque exécution de test de charge est effectuée avec le nombre de requêtes ajusté : make load-test LOAD_TEST_RATE=X.

50 requêtes par seconde

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Faites attention aux deux graphiques du haut. En haut à gauche montre que notre application traite 50 requêtes par seconde (elle pense) et en haut à droite montre la durée de chaque requête. Ces deux paramètres nous aident à regarder et à analyser si nous sommes ou non dans nos limites de performance. Ligne rouge sur le graphique Latence des requêtes HTTP affiche SLO à 60 ms. La ligne montre que nous sommes bien en dessous de notre temps de réponse maximum.

Regardons le côté des coûts :

10000 50 requêtes par seconde / 200 requêtes par serveur = 1 serveurs + XNUMX

Nous pouvons encore améliorer ce chiffre.

500 requêtes par seconde

Des choses plus intéressantes commencent à se produire lorsque la charge atteint 500 requêtes par seconde :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Encore une fois, dans le graphique en haut à gauche, vous pouvez voir que l'application enregistre une charge normale. Si ce n'est pas le cas, il y a un problème sur le serveur sur lequel l'application s'exécute. Le graphique de latence de réponse est situé en haut à droite, montrant que 500 requêtes par seconde entraînaient un délai de réponse de 25 à 40 ms. Le 99e centile s'inscrit toujours bien dans le SLO de 60 ms choisi ci-dessus.

En termes de coût :

10000 500 requêtes par seconde / 20 requêtes par serveur = 1 serveurs + XNUMX

Tout peut encore être amélioré.

1000 requêtes par seconde

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Super lancement ! L'application montre qu'elle a traité 1000 99 requêtes par seconde, mais que la limite de latence a été violée par le SLO. Cela peut être vu à la ligne p100 dans le graphique en haut à droite. Malgré le fait que la ligne p60 soit beaucoup plus élevée, les délais réels sont supérieurs au maximum de XNUMX ms. Passons au profilage pour découvrir ce que fait réellement l'application.

Profilage

Pour le profilage, nous définissons la charge sur 1000 XNUMX requêtes par seconde, puis utilisons pprof pour capturer des données afin de savoir où l'application passe du temps CPU. Cela peut être fait en activant le point de terminaison HTTP pprof, puis, sous charge, enregistrez les résultats à l'aide de curl :

$ curl http://localhost:8080/debug/pprof/profile?seconds=29 > cpu.1000_reqs_sec_no_optimizations.prof

Les résultats peuvent être affichés comme ceci :

$ go tool pprof -http=:12345 cpu.1000_reqs_sec_no_optimizations.prof

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Le graphique montre où et combien l'application dépense du temps CPU. D'après la description de Brendan Gregg:

L'axe X est la population du profil de pile, triée par ordre alphabétique (ce n'est pas l'heure), l'axe Y montre la profondeur de la pile, en comptant de zéro en [haut]. Chaque rectangle est un cadre de pile. Plus le cadre est large, plus il est présent dans les piles. Ce qui se trouve en haut fonctionne sur le processeur, et ce qui se trouve en dessous se trouvent les éléments enfants. Les couleurs ne veulent généralement rien dire, mais sont simplement choisies au hasard pour différencier les montures.

Analyse - hypothèse

Pour le réglage, nous nous concentrerons sur la recherche du temps CPU perdu. Nous rechercherons les plus grandes sources de dépenses inutiles et les supprimerons. Eh bien, étant donné que le profilage révèle très précisément où exactement l'application passe son temps processeur, vous devrez peut-être le faire plusieurs fois, et vous devrez également modifier le code source de l'application, réexécuter les tests et voir que les performances se rapprochent de l'objectif.

Suivant les recommandations de Brendan Gregg, nous lirons le graphique de haut en bas. Chaque ligne affiche un cadre de pile (appel de fonction). La première ligne est le point d'entrée dans le programme, le parent de tous les autres appels (en d'autres termes, tous les autres appels l'auront sur leur pile). La ligne suivante est déjà différente :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Si vous passez le curseur sur le nom d'une fonction sur le graphique, la durée totale pendant laquelle elle était sur la pile pendant le débogage sera affichée. La fonction HTTPServe était présente 65 % du temps, les autres fonctions d'exécution runtime.mcall, mstart и gc, a pris le reste du temps. Fait amusant : 5 % du temps total est consacré aux requêtes DNS :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Les adresses recherchées par le programme appartiennent à Postgresql. Cliquer sur FindByAge:

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Il est intéressant de noter que le programme montre qu'il existe en principe trois sources principales qui ajoutent des délais : l'ouverture et la fermeture des connexions, la demande de données et la connexion à la base de données. Le graphique montre que les requêtes DNS, l'ouverture et la fermeture des connexions occupent environ 13 % du temps d'exécution total.

Hypothèse: La réutilisation des connexions via le pooling devrait réduire le temps d'une seule requête HTTP, permettant un débit plus élevé et une latence plus faible..

Mise en place de l'application - expérimenter

Nous mettons à jour le code source, essayons de supprimer la connexion à Postgresql pour chaque requête. La première option consiste à utiliser pool de connexion au niveau des applications. Dans cette expérience nous installons-le regroupement de connexions à l'aide du pilote SQL pour Go :

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)

if err != nil {
   return nil, err
}

Exécution, observation, analyse

Après avoir relancé le test à 1000 requêtes par seconde, force est de constater que les niveaux de latence du p99 sont revenus à la normale avec un SLO de 60ms !

Quel est le coût ?

10000 1000 requêtes par seconde / 10 requêtes par serveur = 1 serveurs + XNUMX

Faisons encore mieux !

2000 requêtes par seconde

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Doubler la charge montre la même chose, le graphique en haut à gauche montre que l'application parvient à traiter 2000 requêtes par seconde, p100 est inférieur à 60 ms, p99 satisfait le SLO.

En termes de coût :

10000 2000 requêtes par seconde / 5 requêtes par serveur = 1 serveurs + XNUMX

3000 requêtes par seconde

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Ici, l'application peut traiter 3000 requêtes avec une latence p99 inférieure à 60 ms. Le SLO n'est pas violé et le coût est accepté comme suit :

10000 3000 requêtes par seconde / pour 4 1 requêtes par serveur = XNUMX serveurs + XNUMX (l'auteur a arrondi, environ. traducteur)

Essayons une autre série d'analyses.

Analyse - hypothèse

Nous collectons et affichons les résultats du débogage de l'application à 3000 requêtes par seconde :

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Pourtant, 6 % du temps est consacré à l’établissement de connexions. La configuration du pool a amélioré les performances, mais vous pouvez toujours voir que l'application continue de créer de nouvelles connexions à la base de données.

Hypothèse: Les connexions, malgré la présence d'un pool, sont toujours supprimées et nettoyées, l'application doit donc les réinitialiser. La définition du nombre de connexions en attente sur la taille du pool devrait contribuer à réduire la latence en minimisant le temps passé par l'application à créer une connexion..

Mise en place de l'application - expérimenter

Essayer d'installer MaxIdleConns égale à la taille de la piscine (également décrite ici):

db, err := sql.Open("postgres", dbConnectionString)
db.SetMaxOpenConns(8)
db.SetMaxIdleConns(8)
if err != nil {
   return nil, err
}

Exécution, observation, analyse

3000 requêtes par seconde

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

p99 est inférieur à 60 ms avec nettement moins de p100 !

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

La vérification du graphique de flamme montre que la connexion n'est plus perceptible ! Vérifions plus en détail pg(*conn).query - nous ne remarquons pas non plus que la connexion est établie ici.

SRE : Analyse des performances. Méthode de configuration à l'aide d'un simple serveur web dans Go

Conclusion

L'analyse des performances est essentielle pour comprendre que les attentes des clients et les exigences non fonctionnelles sont satisfaites. L'analyse en comparant les observations avec les attentes des clients peut aider à déterminer ce qui est acceptable et ce qui ne l'est pas. Go fournit des outils puissants intégrés à la bibliothèque standard qui rendent l'analyse simple et accessible.

Source: habr.com

Ajouter un commentaire