Niedawno otrzymałem od kogoś ze społeczności e-mail z pytaniem o wydarzenie CLR_MANUAL_EVENT typ oczekiwania; w szczególności, jak rozwiązywać problemy z tym oczekiwaniem, które nagle stało się powszechne w przypadku istniejącego obciążenia, które w dużym stopniu opierało się na typach danych przestrzennych i zapytaniach przy użyciu metod przestrzennych w programie SQL Server.
Jako konsultant prawie zawsze moje pierwsze pytanie brzmi:„Co się zmieniło?” Ale w tym przypadku, podobnie jak w wielu innych, zapewniono mnie, że nic się nie zmieniło w kodzie aplikacji lub wzorcach obciążenia. Więc moim pierwszym przystankiem było podciągnięcie CLR_MANUAL_EVENT poczekaj w bibliotece SQLskills.com Wait Types Library, aby zobaczyć, jakie inne informacje zebraliśmy już na temat tego typu oczekiwania, ponieważ nie jest to zwykle oczekiwanie, z którym widzę problemy w SQL Server. To, co naprawdę mnie zainteresowało, to wykres/mapa cieplna wystąpień dla tego typu oczekiwania, dostarczona przez SentryOne na górze strony:
Fakt, że nie zebrano żadnych danych dla tego typu przez dobry przekrój ich klientów, naprawdę potwierdził dla mnie, że nie jest to powszechny problem, więc zaintrygował mnie fakt, że ten konkretny nakład pracy teraz wykazywał problemy z tym czekać. Nie byłem pewien, gdzie się udać, aby dalej zbadać problem, więc odpowiedziałem na e-mail, mówiąc, że przepraszam, że nie mogłem dalej pomóc, ponieważ nie miałem pojęcia, co spowodowałoby dosłownie dziesiątki wątków wykonujących zapytania przestrzenne do nagle zacznij czekać 2-4 sekundy na raz w tym typie oczekiwania.
Dzień później otrzymałem uprzejmy e-mail uzupełniający od osoby, która zadała pytanie, która poinformowała mnie, że problem został rozwiązany. Rzeczywiście, w rzeczywistym obciążeniu aplikacji nic się nie zmieniło, ale nastąpiła zmiana w środowisku. Pakiet oprogramowania innej firmy został zainstalowany na wszystkich serwerach w ich infrastrukturze przez ich zespół ds. bezpieczeństwa, a to oprogramowanie zbierało dane w odstępach pięciominutowych i powodowało, że przetwarzanie wyrzucania śmieci .NET działało niezwykle agresywnie i „wariowało”, gdy oni powiedzieli. Uzbrojony w te informacje i część mojej wcześniejszej wiedzy na temat rozwoju .NET, zdecydowałem, że chcę się z tym pobawić i sprawdzić, czy mogę odtworzyć zachowanie i jak możemy dalej rozwiązywać przyczyny.
Informacje podstawowe
Przez lata zawsze śledziłem blog PSSQL na MSDN i zwykle jest to jedna z moich ulubionych lokalizacji, gdy przypominam sobie, że czytałem o problemie związanym z SQL Server w pewnym momencie w przeszłości, ale mogę” t zapamiętać wszystkie szczegóły.
Jest post na blogu zatytułowany Wysokie oczekiwania na CLR_MANUAL_EVENT i CLR_AUTO_EVENT autorstwa Jacka Li z 2008 r., który wyjaśnia, dlaczego te oczekiwania można bezpiecznie zignorować w zbiorze sys.dm_os_wait_stats DMV, ponieważ oczekiwania występują w normalnych warunkach, ale nie dotyczy tego, co zrobić, jeśli czasy oczekiwania są zbyt długie lub co może powodować ich wyświetlanie w wielu wątkach w sys.dm_os_waiting_tasks aktywnie.
Jest inny wpis na blogu autorstwa Jacka Li z 2013 roku zatytułowany Problem z wydajnością związany z usuwaniem śmieci CLR i ustawieniem koligacji procesora SQL do której odwołuję się w naszej klasie dostrajania wydajności IEPTO2, kiedy mówię o kwestiach dotyczących wielu instancji i o tym, jak .NET Garbage Collector (GC) wyzwalany przez jedną instancję może wpływać na inne instancje na tym samym serwerze.
GC w .NET istnieje w celu zmniejszenia zużycia pamięci przez aplikacje korzystające z CLR, umożliwiając automatyczne czyszczenie pamięci przydzielonej do obiektów, eliminując w ten sposób potrzebę ręcznej obsługi alokacji i cofania alokacji pamięci przez programistów w stopniu wymaganym przez kod niezarządzany . Funkcjonalność GC jest udokumentowana w Books Online, jeśli chcesz dowiedzieć się więcej o tym, jak to działa, ale szczegóły poza faktem, że kolekcje mogą być blokowane, nie są ważne dla rozwiązywania problemów z aktywnymi oczekiwaniami na CLR_MANUAL_EVENT w SQL Server dalej.
Dotarcie do źródła problemu
Wiedząc, że przyczyną problemu było odśmiecanie przez platformę .NET, postanowiłem poeksperymentować z pojedynczym zapytaniem przestrzennym przeciwko AdventureWorks2016 oraz bardzo prosty skrypt PowerShell do ręcznego wywoływania garbage collectora w pętli, aby śledzić, co dzieje się w sys.dm_os_waiting_tasks wewnątrz SQL Server dla zapytania:
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);
To zapytanie porównuje wszystkie adresy w Person.Address tabeli obok siebie, aby znaleźć dowolny adres, który znajduje się w promieniu 100 metrów od dowolnego innego adresu w tabeli. Tworzy to długotrwałe zadanie równoległe wewnątrz SQL Server, które również generuje duży wynik kartezjański. Jeśli zdecydujesz się odtworzyć to zachowanie na własną rękę, nie oczekuj, że to się zakończy lub zwróci wyniki. Gdy zapytanie jest uruchomione, wątek nadrzędny zadania zaczyna czekać na CXPACKET czeka, a zapytanie kontynuuje przetwarzanie przez kilka minut. Jednak to, co mnie interesowało, to to, co dzieje się, gdy wyrzucanie elementów bezużytecznych dzieje się w środowisku uruchomieniowym CLR lub jeśli GC jest wywoływany, więc użyłem prostego skryptu PowerShell, który zapętlił się i ręcznie wymuszał uruchomienie GC.
UWAGA:TO NIE JEST ZALECANA PRAKTYKA W KODZIE PRODUKCYJNYM Z WIELE POWODÓW!
while (1 -eq 1) {[System.GC]::Collect() }
Po uruchomieniu okna PowerShell prawie natychmiast zacząłem widzieć CLR_MANUAL_EVENT czeka występujące w równoległych wątkach podzadań (pokazanych poniżej, gdzie exec_context_id jest większe od zera) w sys.dm_os_waiting_tasks :
Teraz, gdy mogłem wywołać to zachowanie i zaczęło stawać się jasne, że SQL Server niekoniecznie jest tutaj problemem i może być po prostu ofiarą innej aktywności, chciałem wiedzieć, jak dokopać się głębiej i wskazać przyczynę problemu . W tym miejscu PerfMon przydał się do śledzenia grupy liczników .NET CLR Memory dla wszystkich zadań na serwerze.
Ten zrzut ekranu został zmniejszony, aby pokazać kolekcje dla sqlservr i powershell jako aplikacje w porównaniu z _Global_ kolekcje przez środowisko uruchomieniowe platformy .NET. Wymuszając GC.Collect() aby działać bez przerwy, widzimy, że powershell wystąpienie steruje kolekcjami GC na serwerze. Korzystając z tej grupy liczników PerfMon, możemy śledzić, które aplikacje wykonują najwięcej kolekcji, a następnie kontynuować dalsze badanie problemu. W tym przypadku proste zatrzymanie skryptu PowerShell eliminuje CLR_MANUAL_EVENT czeka wewnątrz SQL Server, a zapytanie kontynuuje przetwarzanie, dopóki nie zatrzymamy go lub pozwolimy na zwrócenie miliarda wierszy wyników, które zostałyby przez nie wyprowadzone.
Wniosek
Jeśli masz aktywne oczekiwania na CLR_MANUAL_EVENT powodując spowolnienia aplikacji, nie zakładaj automatycznie, że problem istnieje wewnątrz SQL Server. SQL Server używa wyrzucania elementów bezużytecznych na poziomie serwera (przynajmniej przed SQL Server 2017 CU4, gdzie małe serwery z mniej niż 2 GB pamięci RAM mogą używać wyrzucania elementów bezużytecznych na poziomie klienta w celu zmniejszenia zużycia zasobów). Jeśli zobaczysz ten problem występujący w programie SQL Server, użyj grupy liczników pamięci .NET CLR w programie PerfMon i sprawdź, czy inna aplikacja steruje wyrzucaniem elementów bezużytecznych w środowisku CLR i w rezultacie blokuje zadania CLR wewnętrznie w programie SQL Server.