Di recente ho ricevuto un'email di domanda da qualcuno della community sul CLR_MANUAL_EVENT tipo di attesa; in particolare, come risolvere i problemi con questa attesa che diventa improvvisamente prevalente per un carico di lavoro esistente che si basava molto sui tipi di dati spaziali e sulle query usando i metodi spaziali in SQL Server.
Come consulente, la mia prima domanda è quasi sempre:"Cosa è cambiato?" Ma in questo caso, come in tanti casi, mi è stato assicurato che nulla era cambiato con il codice dell'applicazione o con i modelli di carico di lavoro. Quindi la mia prima tappa è stata quella di fermare il CLR_MANUAL_EVENT wait nella libreria dei tipi di attesa di SQLskills.com per vedere quali altre informazioni avevamo già raccolto su questo tipo di attesa, poiché non è comunemente un'attesa con cui vedo problemi in SQL Server. Quello che ho trovato davvero interessante è stato il grafico/mappa termica delle occorrenze per questo tipo di attesa fornito da SentryOne nella parte superiore della pagina:
Il fatto che non siano stati raccolti dati per questo tipo in una buona sezione trasversale dei loro clienti mi ha davvero confermato che questo non è qualcosa che comunemente è un problema, quindi sono stato incuriosito dal fatto che questo carico di lavoro specifico ora mostrasse problemi con questa attesa. Non ero sicuro di dove andare per indagare ulteriormente sul problema, quindi ho risposto all'e-mail dicendo che mi dispiaceva di non poter aiutare ulteriormente perché non avevo idea di cosa avrebbe causato letteralmente dozzine di thread che eseguivano query spaziali all'improvviso inizia a dover attendere 2-4 secondi alla volta su questo tipo di attesa.
Il giorno dopo, ho ricevuto una gentile e-mail di follow-up dalla persona che ha posto la domanda che mi ha informato di aver risolto il problema. In effetti, non era cambiato nulla nel carico di lavoro effettivo dell'applicazione, ma si è verificata una modifica all'ambiente. Un pacchetto software di terze parti è stato installato su tutti i server nella loro infrastruttura dal loro team di sicurezza e questo software raccoglieva dati a intervalli di cinque minuti e faceva sì che l'elaborazione della raccolta dei rifiuti .NET funzionasse in modo incredibilmente aggressivo e "impazziva" come loro hanno detto. Forte di queste informazioni e di alcune delle mie conoscenze passate sullo sviluppo di .NET, ho deciso di voler giocare un po' con queste informazioni e vedere se potevo riprodurre il comportamento e come risolvere ulteriormente le cause.
Informazioni di base
Nel corso degli anni ho sempre seguito il blog PSSQL su MSDN, e di solito è una delle mie posizioni di riferimento quando ricordo di aver letto in passato di un problema relativo a SQL Server, ma posso ' Non ricordo tutti i dettagli.
C'è un post sul blog intitolato Alte attese su CLR_MANUAL_EVENT e CLR_AUTO_EVENT di Jack Li del 2008 che spiega perché queste attese possono essere tranquillamente ignorate nell'aggregato sys.dm_os_wait_stats DMV poiché le attese si verificano in condizioni normali, ma non risolve cosa fare se i tempi di attesa sono eccessivamente lunghi o cosa potrebbe causarne la visualizzazione su più thread in sys.dm_os_waiting_tasks attivamente.
C'è un altro post sul blog di Jack Li del 2013 intitolato Un problema di prestazioni che coinvolge la raccolta di dati inutili CLR e l'impostazione dell'affinità della CPU SQL a cui faccio riferimento nella nostra classe di ottimizzazione delle prestazioni IEPTO2 quando parlo di considerazioni su più istanze e di come .NET Garbage Collector (GC) attivato da un'istanza può influire sulle altre istanze sullo stesso server.
Il GC in .NET esiste per ridurre l'utilizzo della memoria delle applicazioni che utilizzano CLR consentendo la pulizia automatica della memoria allocata agli oggetti, eliminando così la necessità per gli sviluppatori di dover gestire manualmente l'allocazione e la deallocazione della memoria nella misura richiesta dal codice non gestito . La funzionalità GC è documentata nella documentazione in linea se desideri saperne di più su come funziona, ma le specifiche oltre al fatto che le raccolte possono essere bloccate non sono importanti per la risoluzione dei problemi di attese attive su CLR_MANUAL_EVENT in SQL Server ulteriormente.
Arrivare alla radice del problema
Sapendo che la Garbage Collection da parte di .NET era la causa del problema, ho deciso di fare qualche esperimento utilizzando una singola query spaziale su AdventureWorks2016 e uno script PowerShell molto semplice per richiamare manualmente il Garbage Collector in un ciclo per tenere traccia di ciò che accade in sys.dm_os_waiting_tasks all'interno di SQL Server per la query:
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);
Questa query sta confrontando tutti gli indirizzi in Person.Address tavolo l'uno contro l'altro per trovare qualsiasi indirizzo che si trovi entro 100 metri da qualsiasi altro indirizzo nella tabella. Questo crea un'attività parallela di lunga durata all'interno di SQL Server che produce anche un grande risultato cartesiano. Se decidi di riprodurre questo comportamento da solo, non aspettarti che venga completato o restituisca i risultati. Con la query in esecuzione, il thread padre per l'attività inizia ad attendere su CXPACKET attende e la query continua l'elaborazione per alcuni minuti. Tuttavia, ciò che mi interessava era cosa succede quando la raccolta dei rifiuti avviene nel runtime CLR o se viene richiamato il GC, quindi ho usato un semplice script di PowerShell che eseguiva il ciclo e forzava manualmente l'esecuzione del GC.
NOTA:QUESTA NON È UNA PRATICA RACCOMANDATA NEL CODICE DI PRODUZIONE PER MOLTE RAGIONI!
while (1 -eq 1) {[System.GC]::Collect() }
Una volta che la finestra di PowerShell è stata in esecuzione, ho iniziato quasi immediatamente a vedere CLR_MANUAL_EVENT attese che si verificano sui thread delle attività secondarie parallele (mostrate di seguito, dove exec_context_id è maggiore di zero) in sys.dm_os_waiting_tasks :
Ora che ho potuto attivare questo comportamento e stava iniziando a diventare chiaro che SQL Server non è necessariamente il problema qui e potrebbe essere solo vittima di altre attività, volevo sapere come scavare più a fondo e individuare la causa principale del problema . È qui che PerfMon è stato utile per tenere traccia del gruppo di contatori di memoria CLR .NET per tutte le attività sul server.
Questa schermata è stata ridotta per mostrare le raccolte per sqlservr e powershell come applicazioni rispetto a _Global_ raccolte dal runtime .NET. Forzando GC.Collect() per funzionare costantemente possiamo vedere che il powershell l'istanza sta guidando le raccolte GC sul server. Utilizzando questo gruppo di contatori PerfMon possiamo tenere traccia delle applicazioni che eseguono più raccolte e da lì continuare ulteriori indagini sul problema. In questo caso, il semplice arresto dello script di PowerShell elimina il CLR_MANUAL_EVENT attende all'interno di SQL Server e la query continua l'elaborazione fino a quando non la fermiamo o le consentiamo di restituire il miliardo di righe di risultati che verrebbero generati da essa.
Conclusione
Se hai attese attive per CLR_MANUAL_EVENT causando rallentamenti delle applicazioni, non presupporre automaticamente che il problema esista all'interno di SQL ServerSQL Server. SQL Server utilizza la Garbage Collection a livello di server (almeno prima di SQL Server 2017 CU4 in cui i server di piccole dimensioni con meno di 2 GB di RAM possono utilizzare la Garbage Collection a livello di client per ridurre l'utilizzo delle risorse). Se si verifica questo problema in SQL Server, utilizzare il gruppo di contatori di memoria .NET CLR in PerfMon e verificare se un'altra applicazione sta guidando la Garbage Collection in CLR e di conseguenza blocca le attività CLR internamente in SQL Server.