sql >> Base de Datos >  >> RDS >> Database

Seguimiento de esperas altas de CLR_MANUAL_EVENT

Recientemente recibí una pregunta por correo electrónico de alguien de la comunidad sobre el CLR_MANUAL_EVENT tipo de espera; específicamente, cómo solucionar problemas con esta espera repentinamente predominante para una carga de trabajo existente que dependía en gran medida de tipos de datos espaciales y consultas mediante los métodos espaciales en SQL Server.

Como consultor, mi primera pregunta casi siempre es:"¿Qué ha cambiado?" Pero en este caso, como en tantos casos, me aseguraron que nada había cambiado con el código de la aplicación o los patrones de carga de trabajo. Así que mi primera parada fue abrir el CLR_MANUAL_EVENT espere en la Biblioteca de tipos de espera de SQLskills.com para ver qué otra información ya habíamos recopilado sobre este tipo de espera, ya que normalmente no es una espera con la que veo problemas en SQL Server. Lo que encontré realmente interesante fue el gráfico/mapa de calor de ocurrencias para este tipo de espera proporcionado por SentryOne en la parte superior de la página:

El hecho de que no se hayan recopilado datos para este tipo en una buena muestra representativa de sus clientes realmente me confirmó que esto no es algo que comúnmente sea un problema, por lo que me intrigó el hecho de que esta carga de trabajo específica ahora exhibía problemas con esta espera. No estaba seguro de adónde ir para investigar más a fondo el problema, así que respondí al correo electrónico diciendo que lamentaba no poder ayudar más porque no tenía idea de qué estaría causando literalmente docenas de subprocesos que realizan consultas espaciales para de repente comienza a tener que esperar de 2 a 4 segundos a la vez en este tipo de espera.

Un día después, recibí un amable correo electrónico de seguimiento de la persona que hizo la pregunta que me informó que había resuelto el problema. De hecho, no había cambiado nada en la carga de trabajo real de la aplicación, pero se produjo un cambio en el entorno. El equipo de seguridad instaló un paquete de software de terceros en todos los servidores de su infraestructura, y este software recopilaba datos a intervalos de cinco minutos y provocaba que el procesamiento de recolección de elementos no utilizados de .NET se ejecutara de manera increíblemente agresiva y "se volviera loco" como ellos dijeron. Armado con esta información y algunos de mis conocimientos previos sobre el desarrollo de .NET, decidí que quería jugar un poco con esto y ver si podía reproducir el comportamiento y cómo podíamos seguir solucionando las causas.

Información general

A lo largo de los años, siempre he seguido el blog de PSSQL en MSDN, y esa suele ser una de mis ubicaciones a las que recurro cuando recuerdo que leí sobre un problema relacionado con SQL Server en algún momento en el pasado, pero no puedo. No recuerdo todos los detalles.

Hay una publicación de blog titulada Esperas altas en CLR_MANUAL_EVENT y CLR_AUTO_EVENT por Jack Li de 2008 que explica por qué estas esperas se pueden ignorar de manera segura en el agregado sys.dm_os_wait_stats DMV, ya que las esperas ocurren en condiciones normales, pero no aborda qué hacer si los tiempos de espera son excesivamente largos o qué podría estar causando que se vean en varios subprocesos en sys.dm_os_waiting_tasks activamente.

Hay otra publicación de blog de Jack Li de 2013 titulada Un problema de rendimiento relacionado con la recolección de elementos no utilizados de CLR y la configuración de afinidad de CPU de SQL al que hago referencia en nuestra clase de ajuste de rendimiento de IEPTO2 cuando hablo de las consideraciones de instancias múltiples y cómo el Recolector de elementos no utilizados (GC) de .NET activado por una instancia puede afectar a las otras instancias en el mismo servidor.

El GC en .NET existe para reducir el uso de memoria de las aplicaciones que usan CLR al permitir que la memoria asignada a los objetos se limpie automáticamente, eliminando así la necesidad de que los desarrolladores tengan que manejar manualmente la asignación y desasignación de memoria en la medida requerida por el código no administrado. . La funcionalidad de GC está documentada en los Libros en línea si desea obtener más información sobre cómo funciona, pero los detalles más allá del hecho de que las colecciones pueden bloquearse no son importantes para solucionar problemas de esperas activas en CLR_MANUAL_EVENT en SQL Server más.

Llegar a la raíz del problema

Con el conocimiento de que la recolección de elementos no utilizados por parte de .NET era lo que estaba causando el problema, decidí hacer un poco de experimentación usando una sola consulta espacial contra AdventureWorks2016 y un script de PowerShell muy simple para invocar el recolector de basura manualmente en un bucle para rastrear lo que sucede en sys.dm_os_waiting_tasks dentro de SQL Server para la consulta:

USE AdventureWorks2016;
GO  
SELECT a.SpatialLocation.ToString(), a.City, b.SpatialLocation.ToString(), b.City
  FROM Person.Address AS a
  INNER JOIN Person.Address AS b ON a.SpatialLocation.STDistance(b.SpatialLocation) <= 100 
  ORDER BY a.SpatialLocation.STDistance(b.SpatialLocation); 

Esta consulta compara todas las direcciones en Person.Address tabla uno contra el otro para encontrar cualquier dirección que esté dentro de los 100 metros de cualquier otra dirección en la tabla. Esto crea una tarea paralela de larga duración dentro de SQL Server que también produce un gran resultado cartesiano. Si decide reproducir este comportamiento por su cuenta, no espere que esto se complete o devuelva resultados. Con la consulta en ejecución, el subproceso principal de la tarea comienza a esperar en CXPACKET espera y la consulta continúa procesándose durante varios minutos. Sin embargo, lo que me interesaba era lo que sucede cuando ocurre la recolección de elementos no utilizados en el tiempo de ejecución de CLR o si se invoca el GC, por lo que usé un script de PowerShell simple que haría un bucle y forzaría manualmente la ejecución del GC.

NOTA:¡ESTA NO ES UNA PRÁCTICA RECOMENDADA EN EL CÓDIGO DE PRODUCCIÓN POR MUCHAS RAZONES!

while (1 -eq 1) {[System.GC]::Collect() }

Una vez que se estaba ejecutando la ventana de PowerShell, casi de inmediato comencé a ver CLR_MANUAL_EVENT esperas que ocurren en los subprocesos de subtareas paralelas (que se muestran a continuación, donde exec_context_id es mayor que cero) en sys.dm_os_waiting_tasks :

Ahora que podía desencadenar este comportamiento y comenzaba a quedar claro que SQL Server no es necesariamente el problema aquí y puede que solo sea víctima de otra actividad, quería saber cómo profundizar e identificar la causa raíz del problema. . Aquí es donde PerfMon resultó útil para rastrear el grupo de contadores de memoria .NET CLR para todas las tareas en el servidor.

Esta captura de pantalla se ha reducido para mostrar las colecciones de sqlservr y powershell como aplicaciones en comparación con _Global_ colecciones por el tiempo de ejecución de .NET. Forzando GC.Collect() para ejecutarse constantemente podemos ver que el powershell instancia está impulsando las colecciones de GC en el servidor. Al usar este grupo de contadores PerfMon, podemos rastrear qué aplicaciones están realizando la mayoría de las recopilaciones y, a partir de ahí, continuar con la investigación del problema. Para este caso, simplemente detener el script de PowerShell elimina el CLR_MANUAL_EVENT espera dentro de SQL Server y la consulta continúa procesándose hasta que la detenemos o permitimos que devuelva los mil millones de filas de resultados que generaría.

Conclusión

Si tiene esperas activas para CLR_MANUAL_EVENT causando ralentizaciones de la aplicación, no asuma automáticamente que el problema existe dentro de SQL Server. SQL Server usa la recolección de elementos no utilizados a nivel de servidor (al menos antes de SQL Server 2017 CU4, donde los servidores pequeños con menos de 2 GB de RAM pueden usar la recolección de elementos no utilizados a nivel de cliente para reducir el uso de recursos). Si ve que este problema ocurre en SQL Server, use el grupo de contadores de memoria .NET CLR en PerfMon y verifique si otra aplicación está impulsando la recolección de elementos no utilizados en CLR y, como resultado, está bloqueando las tareas de CLR internamente en SQL Server.