¿Por qué tenemos un aumento repentino en los tiempos de respuesta?

12

Tenemos una API que se implementa utilizando ServiceStack que está alojado en IIS. Al realizar pruebas de carga de la API, descubrimos que los tiempos de respuesta son buenos, pero que se deterioran rápidamente tan pronto como llegamos a unos 3.500 usuarios simultáneos por servidor. Tenemos dos servidores y cuando los conectamos con 7,000 usuarios, los tiempos de respuesta promedio se sitúan por debajo de los 500 ms para todos los puntos finales. Los cuadros están detrás de un equilibrador de carga, por lo que obtenemos 3.500 concurrentes por servidor. Sin embargo, tan pronto como aumentamos el número total de usuarios concurrentes, vemos un aumento significativo en los tiempos de respuesta. El aumento de los usuarios concurrentes a 5,000 por servidor nos da un tiempo de respuesta promedio por punto final de alrededor de 7 segundos.

La memoria y la CPU en los servidores son bastante bajas, tanto mientras los tiempos de respuesta son buenos como cuando se deterioran. En el pico con 10,000 usuarios simultáneos, el promedio de CPU es inferior al 50% y la RAM se ubica alrededor de 3-4 GB de 16. Esto nos deja pensando que estamos llegando a algún tipo de límite en alguna parte. La siguiente captura de pantalla muestra algunos contadores clave en perfmon durante una prueba de carga con un total de 10,000 usuarios simultáneos. El contador resaltado es solicitudes / segundo. A la derecha de la captura de pantalla, puede ver que el gráfico de solicitudes por segundo se vuelve realmente errático. Este es el indicador principal para tiempos de respuesta lentos. Tan pronto como vemos este patrón, notamos tiempos de respuesta lentos en la prueba de carga.

captura de pantalla de perfmon con solicitudes por segundo resaltadas

¿Cómo hacemos para solucionar este problema de rendimiento? Estamos tratando de identificar si esto es un problema de codificación o un problema de configuración. ¿Hay alguna configuración en web.config o IIS que pueda explicar este comportamiento? El grupo de aplicaciones ejecuta .NET v4.0 y la versión de IIS es 7.5. El único cambio que hemos realizado desde la configuración predeterminada es actualizar el valor de la longitud de la cola del grupo de aplicaciones de 1,000 a 5,000. También hemos agregado la siguiente configuración al archivo Aspnet.config:

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

Más detalles:

El propósito de la API es combinar datos de varias fuentes externas y devolver como JSON. Actualmente está utilizando una implementación de caché InMemory para almacenar en caché las llamadas externas individuales en la capa de datos. La primera solicitud a un recurso obtendrá todos los datos requeridos y cualquier solicitud posterior para el mismo recurso obtendrá resultados de la memoria caché. Tenemos un 'corredor de caché' que se implementa como un proceso en segundo plano que actualiza la información en el caché a ciertos intervalos establecidos. Hemos agregado bloqueo alrededor del código que obtiene datos de los recursos externos. También hemos implementado los servicios para obtener los datos de las fuentes externas de forma asíncrona, de modo que el punto final solo sea tan lento como la llamada externa más lenta (a menos que tengamos datos en el caché, por supuesto). Esto se hace usando la clase System.Threading.Tasks.Task.¿Podríamos estar llegando a una limitación en términos de número de hilos disponibles para el proceso?

Christian Hagelid
fuente
55
¿Cuántos núcleos tiene tu CPU? Tal vez estás maximizando un núcleo. Cuando el número mágico es 50%, 25% o 12.5%, eso sugiere que has maximizado un núcleo y por alguna razón no puedes usar los otros núcleos que están inactivos. Compruebe si hay un núcleo al máximo.
David Schwartz el
1
¿Tienes un hilo por solicitud? Entonces, para 5000 solicitudes, ¿tienes 5000 hilos? Si lo hace, entonces ese es probablemente su problema. En su lugar, debe crear un grupo de subprocesos y usar el grupo de subprocesos para procesar las solicitudes, poniendo en cola las solicitudes a medida que ingresan al grupo de subprocesos. Cuando un hilo ha terminado con una solicitud, puede procesar una solicitud fuera de la cola. Este tipo de discusión es mejor para stackoverflow. Demasiados hilos significa demasiados cambios de contexto.
Matt
1
Solo una comprobación de cordura aquí, ¿ha intentado desactivar todos sus procesos en segundo plano y ver cuál es el comportamiento solo para el JSON que devuelve datos estáticos del caché? En otras palabras, hacer que su JSON solicite datos estáticos y eliminar las "llamadas asíncronas externas" que actualizan su caché por completo. Además, dependiendo de la cantidad de datos JSON que se sirve en cada solicitud, ¿ha pensado en el rendimiento de su red y si las solicitudes comienzan a respaldarse porque los servidores simplemente no pueden enviar los datos lo suficientemente rápido?
Robert
1
+1 a la sugerencia de Davids anterior. Realmente debería rehacer la prueba y observar cuidadosamente cada utilización principal. Te sugiero que hagas esto lo antes posible para eliminarlo si nada más. En segundo lugar, sospecho un poco de tu caché. La contención de la cerradura puede mostrar exactamente este tipo de comportamiento: en algunos puntos críticos, las cerraduras causan demoras que a su vez hacen que las cerraduras se mantengan por más tiempo de lo normal, causando un punto de inflexión donde las cosas descienden rápidamente. ¿Puedes compartir tu código de caché y bloqueo?
Steve Cook
1
¿Cuál es la configuración del disco para los servidores (suponiendo que, dado que tienen una carga equilibrada, la configuración del disco es la misma)? ¿Puedes publicar todas las especificaciones de las unidades / servidores en tu publicación inicial? ¿Ha lanzado un perfmon en los discos en las unidades físicas en las que existen IIS Y los archivos de registro de IIS? Es muy posible que tenga problemas con el disco en que 3,500 solicitudes = 3,500+ entradas de registro IIS. Si están en el mismo disco / partición, podría tener un gran problema allí.
Techie Joe

Respuestas:

2

Siguiendo con @DavidSchwartz y @Matt, esto parece un problema de administración de hilos, bloqueos.

Yo sugiero:

  1. Congele las llamadas externas y la memoria caché generada para ellas y ejecute la prueba de carga con información externa estática solo para descartar cualquier problema no relacionado con el lado del entorno del servidor.

  2. Use grupos de subprocesos si no los usa.

  3. Acerca de las llamadas externas que dijo "También hemos implementado los servicios para obtener los datos de las fuentes externas de forma asíncrona, de modo que el punto final solo sea tan lento como la llamada externa más lenta (a menos que tengamos datos en el caché, por supuesto). "

Las preguntas son: - ¿Ha verificado si algún dato de caché está bloqueado durante la llamada externa o solo al escribir el resultado de la llamada externa en el caché? (demasiado obvio pero debo decir). - ¿Bloqueas todo el caché o partes más pequeñas? (demasiado obvio pero debo decir). - Incluso si son asíncronos, ¿con qué frecuencia se ejecutan las llamadas externas? Incluso si no se ejecutan con tanta frecuencia, podrían bloquearse por una cantidad excesiva de solicitudes al caché de las llamadas del usuario mientras el caché está bloqueado. Este escenario generalmente muestra un porcentaje fijo de CPU utilizado porque muchos subprocesos están esperando en intervalos fijos y el "bloqueo" también debe ser administrado. - ¿Ha verificado si las tareas externas significan que el tiempo de respuesta también aumenta cuando llega el escenario lento?

Si el problema persiste, sugiero evitar la clase Task y hacer las llamadas externas a través del mismo grupo de subprocesos que administra las solicitudes de los usuarios. Esto es para evitar el escenario anterior.

SaintJob 2.0
fuente