Pourquoi obtenons-nous un pic soudain des temps de réponse?

12

Nous avons une API qui est implémentée à l'aide de ServiceStack qui est hébergé dans IIS. En effectuant des tests de charge de l'API, nous avons découvert que les temps de réponse sont bons mais qu'ils se détériorent rapidement dès que nous atteignons environ 3 500 utilisateurs simultanés par serveur. Nous avons deux serveurs et lorsqu'ils atteignent 7 000 utilisateurs, les temps de réponse moyens sont inférieurs à 500 ms pour tous les points de terminaison. Les boîtes sont derrière un équilibreur de charge, nous avons donc 3 500 concurrents par serveur. Cependant, dès que nous augmentons le nombre total d'utilisateurs simultanés, nous constatons une augmentation significative des temps de réponse. L'augmentation du nombre d'utilisateurs simultanés à 5 000 par serveur nous donne un temps de réponse moyen par point de terminaison d'environ 7 secondes.

La mémoire et le processeur sur les serveurs sont assez faibles, à la fois pendant que les temps de réponse sont bons et après s'être détériorés. Au sommet avec 10 000 utilisateurs simultanés, le processeur est en moyenne un peu moins de 50% et la RAM se situe autour de 3-4 Go sur 16. Cela nous laisse penser que nous atteignons une sorte de limite quelque part. La capture d'écran ci-dessous montre certains compteurs clés de perfmon lors d'un test de charge avec un total de 10 000 utilisateurs simultanés. Le compteur en surbrillance est requêtes / seconde. À droite de la capture d'écran, vous pouvez voir le graphique des requêtes par seconde devenir vraiment erratique. Il s'agit du principal indicateur de temps de réponse lents. Dès que nous voyons ce modèle, nous remarquons des temps de réponse lents dans le test de charge.

Capture d'écran de perfmon avec des requêtes par seconde mises en évidence

Comment résoudre ce problème de performances? Nous essayons d'identifier s'il s'agit d'un problème de codage ou d'un problème de configuration. Existe-t-il des paramètres dans web.config ou IIS qui pourraient expliquer ce comportement? Le pool d'applications exécute .NET v4.0 et la version IIS est 7.5. La seule modification que nous avons apportée par rapport aux paramètres par défaut est de mettre à jour la valeur de longueur de file d'attente du pool d'applications de 1 000 à 5 000. Nous avons également ajouté les paramètres de configuration suivants au fichier Aspnet.config:

<system.web>
    <applicationPool 
        maxConcurrentRequestsPerCPU="5000"
        maxConcurrentThreadsPerCPU="0" 
        requestQueueLimit="5000" />
</system.web>

Plus de détails:

Le but de l'API est de combiner les données de diverses sources externes et de les renvoyer en JSON. Il utilise actuellement une implémentation de cache InMemory pour mettre en cache les appels externes individuels au niveau de la couche de données. La première demande à une ressource récupérera toutes les données requises et toute demande ultérieure pour la même ressource obtiendra les résultats du cache. Nous avons un «exécuteur de cache» qui est implémenté comme un processus d'arrière-plan qui met à jour les informations dans le cache à certains intervalles définis. Nous avons ajouté un verrouillage autour du code qui récupère les données des ressources externes. Nous avons également implémenté les services pour extraire les données des sources externes de manière asynchrone afin que le point de terminaison ne soit aussi lent que l'appel externe le plus lent (sauf si nous avons des données dans le cache bien sûr). Cela se fait à l'aide de la classe System.Threading.Tasks.Task.Pourrions-nous atteindre une limitation en termes de nombre de threads disponibles pour le processus?

Christian Hagelid
la source
5
Combien de cœurs possède votre CPU? Peut-être que vous maximisez un cœur. Lorsque le nombre magique est de 50%, 25% ou 12,5%, cela suggère que vous avez atteint un maximum et que pour une raison quelconque, vous ne pouvez pas utiliser les autres cœurs qui sont inactifs. Recherchez un noyau au maximum.
David Schwartz
1
Avez-vous un thread par demande? Donc, pour 5000 requêtes, avez-vous 5000 threads? Si vous le faites, c'est probablement votre problème. Vous devez à la place créer un pool de threads et utiliser le pool de threads pour traiter les demandes, en mettant les demandes en file d'attente à mesure qu'elles arrivent dans le pool de threads. Lorsqu'un thread a terminé avec une demande, il peut traiter une demande hors de la file d'attente. Ce type de discussion est préférable pour stackoverflow. Trop de threads signifie trop de changements de contexte.
Matt
1
Juste une vérification de santé mentale ici, avez-vous essayé de désactiver tous vos processus d'arrière-plan et de voir quel est le comportement du JSON renvoyant des données statiques du cache? En d'autres termes, rendre votre JSON demande des données statiques et supprimer les «appels asynchrones externes» qui actualisent complètement votre cache. En outre, en fonction de la quantité de données JSON servies à chaque demande, avez-vous pensé à votre débit réseau et si les demandes commencent à être sauvegardées parce que les serveurs ne peuvent tout simplement pas pousser les données assez rapidement?
Robert
1
+1 à la suggestion de David ci-dessus. Vous devez vraiment refaire le test et regarder attentivement chaque utilisation de base. Je vous suggère de le faire dès que possible pour l'éliminer si rien d'autre. Deuxièmement, je me méfie un peu de votre cache. La contention des verrous peut montrer exactement ce type de comportement - à certains points critiques, les verrous provoquent des retards qui, à leur tour, maintiennent les verrous plus longtemps que la normale, provoquant un point de basculement où les choses descendent rapidement. Pouvez-vous partager votre code de mise en cache et de verrouillage?
Steve Cook,
1
Quelle est la configuration du disque pour les serveurs (en supposant que comme ils sont équilibrés en charge, la configuration du disque est la même)? Pouvez-vous publier toutes les spécifications des lecteurs / serveurs dans votre message initial? Avez-vous lancé un perfmon sur le (s) disque (s) sur le (s) lecteur (s) physique (s) sur lesquels IIS ET les fichiers journaux IIS existent? Il est fort possible que vous rencontriez des problèmes avec le disque dans la mesure où 3 500 requêtes = 3 500+ journaux IIS entrent. S'ils sont sur le même disque / partition, vous pourriez avoir un gros problème là-bas.
Techie Joe

Réponses:

2

Après @DavidSchwartz et @Matt, cela ressemble à un problème de gestion des verrous.

Je suggère:

  1. Geler les appels externes et le cache généré pour eux et exécuter le test de charge avec des informations externes statiques juste pour éliminer tout problème non lié au côté serveur - environnement.

  2. Utilisez des pools de threads si vous ne les utilisez pas.

  3. À propos des appels externes, vous avez dit "Nous avons également implémenté les services pour extraire les données des sources externes de manière asynchrone afin que le point de terminaison ne soit aussi lent que l'appel externe le plus lent (sauf si nous avons des données dans le cache bien sûr). "

Les questions sont les suivantes: - Avez-vous vérifié si des données de cache sont verrouillées pendant l'appel externe ou uniquement lors de l'écriture du résultat de l'appel externe dans le cache? (trop évident mais faut dire). - Verrouillez-vous la totalité du cache ou de petites parties? (trop évident mais faut dire). - Même s'ils sont asynchrones, à quelle fréquence les appels externes sont-ils exécutés? Même s'ils ne s'exécutent pas si souvent, ils peuvent être bloqués par une quantité excessive de demandes au cache provenant des appels des utilisateurs pendant que le cache est verrouillé. Ce scénario montre généralement un pourcentage fixe de CPU utilisé car de nombreux threads attendent à intervalles fixes et le "verrouillage" doit également être géré. - Avez-vous vérifié si les tâches externes signifient que le temps de réponse augmente également lorsque le scénario lent arrive?

Si le problème persiste, je suggère d'éviter la classe Task et d'effectuer les appels externes via le même pool de threads qui gère les demandes des utilisateurs. C'est pour éviter le scénario précédent.

SaintJob 2.0
la source