Наскоро получих имейл въпрос от някой в общността относно CLR_MANUAL_EVENT тип чакане; по-конкретно, как да отстраните проблеми с това изчакване, което внезапно става преобладаващо за съществуващо работно натоварване, което разчита в голяма степен на типове пространствени данни и заявки, използвайки пространствените методи в SQL Server.
Като консултант първият ми въпрос почти винаги е „Какво се промени?“ Но в този случай, както и в толкова много случаи, бях уверен, че нищо не се е променило с кода на приложението или моделите на работно натоварване. Така че първата ми спирка беше да изкача CLR_MANUAL_EVENT изчакайте в библиотеката на SQLskills.com Wait Types Library, за да видите каква друга информация вече сме събрали за този тип чакане, тъй като обикновено не виждам проблеми в SQL Server. Това, което намерих наистина интересно, беше диаграмата/топлинната карта на събитията за този тип чакане, предоставена от SentryOne в горната част на страницата:
Фактът, че не са събрани данни за този тип в добър напречен разрез на техните клиенти, наистина потвърди за мен, че това не е нещо, което обикновено е проблем, така че бях заинтригуван от факта, че това специфично натоварване сега показва проблеми с това чакане. Не бях сигурен къде да отида, за да проуча допълнително проблема, така че отговорих на имейла, като казах, че съжалявам, че не мога да помогна допълнително, защото нямах никаква представа какво би причинило буквално десетки нишки, изпълняващи пространствени заявки към изведнъж започва да се налага да чакате по 2-4 секунди наведнъж при този тип чакане.
Ден по-късно получих любезен последващ имейл от човека, който зададе въпроса, който ме информира, че са решили проблема. Всъщност нищо в действителното работно натоварване на приложението не се е променило, но е настъпила промяна в средата. Софтуерен пакет на трета страна беше инсталиран на всички сървъри в тяхната инфраструктура от техния екип за сигурност и този софтуер събираше данни на петминутни интервали и караше обработката на .NET събирането на боклук да работи невероятно агресивно и „полудява“ те казаха. Въоръжен с тази информация и някои от предишните ми познания за разработката на .NET реших, че искам да си поиграя с това и да видя дали мога да възпроизведа поведението и как можем да продължим да отстраняваме причините по-нататък.
Основна информация
През годините винаги съм следвал блога на PSSQL в MSDN и това обикновено е едно от местата, където отивам, когато си спомня, че съм чел за проблем, свързан със SQL Server в някакъв момент в миналото, но мога да „ не помня всички подробности.
Има публикация в блога, озаглавена Много чакане на CLR_MANUAL_EVENT и CLR_AUTO_EVENT от Джак Ли от 2008 г., което обяснява защо тези изчаквания могат безопасно да бъдат игнорирани в обобщените sys.dm_os_wait_stats DMV, тъй като изчакванията се случват при нормални условия, но не се отнася за това какво да се прави, ако времето за изчакване е прекалено дълго или какво може да ги накара да се виждат в множество нишки в sys.dm_os_waiting_tasks активно.
Има друга публикация в блог от Джак Ли от 2013 г., озаглавена Проблем с производителността, включващ събиране на боклука в CLR и настройка за афинитет на SQL CPU което споменавам в нашия клас за настройка на производителността на IEPTO2, когато говоря за съображения за множество екземпляри и как .NET Garbage Collector (GC), който се задейства от един екземпляр, може да повлияе на другите екземпляри на същия сървър.
GC в .NET съществува, за да намали използването на паметта от приложения, използващи CLR, като позволява на паметта, разпределена на обекти, да се почиства автоматично, като по този начин елиминира необходимостта разработчиците да трябва ръчно да обработват разпределението и освобождаването на паметта до степента, изисквана от неуправляван код . Функционалността на GC е документирана в Books Online, ако искате да научите повече за това как работи, но спецификата извън факта, че колекциите могат да бъдат блокирани, не са важни за отстраняване на неизправности при активно изчакване на CLR_MANUAL_EVENT в SQL Server по-нататък.
Стигане до корена на проблема
Със знанието, че събирането на боклука от .NET е причината за възникването на проблема, реших да направя някои експерименти, използвайки една пространствена заявка срещу AdventureWorks2016 и много прост скрипт на PowerShell за ръчно извикване на колектора за боклук в цикъл, за да проследите какво се случва в sys.dm_os_waiting_tasks вътре в SQL Server за заявката:
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);
Тази заявка сравнява всички адреси в Person.Address таблица един срещу друг, за да намерите всеки адрес, който е в рамките на 100 метра от всеки друг адрес в таблицата. Това създава продължителна паралелна задача в SQL Server, която също произвежда голям декартов резултат. Ако решите да възпроизведете това поведение сами, не очаквайте това да завърши или да върне резултатите обратно. Когато заявката се изпълнява, родителската нишка за задачата започва да чака на CXPACKET изчаква и заявката продължава да обработва няколко минути. Това, което ме интересуваше обаче, беше какво се случва, когато събирането на боклука се случи по време на изпълнение на CLR или ако GC се извика, така че използвах прост скрипт на PowerShell, който ще завърти и ръчно принуди GC да се стартира.
ЗАБЕЛЕЖКА:ТОВА НЕ Е ПРЕПОРЪЧИТЕЛНА ПРАКТИКА В ПРОИЗВОДСТВЕН КОД ПО МНОГО ПРИЧИНИ!
while (1 -eq 1) {[System.GC]::Collect() }
След като прозорецът на PowerShell стартира, почти веднага започнах да виждам CLR_MANUAL_EVENT изчаквания, възникващи в паралелните нишки на подзадачи (показани по-долу, където exec_context_id е по-голям от нула) в sys.dm_os_waiting_tasks :
Сега, когато можех да задействам това поведение и започна да става ясно, че SQL Server не е непременно проблемът тук и може просто да е жертва на друга дейност, исках да знам как да копая по-дълбоко и да посоча първопричината за проблема . Тук PerfMon беше полезен за проследяване на групата броячи на .NET CLR Memory за всички задачи на сървъра.
Тази екранна снимка е намалена, за да покаже колекциите за sqlservr и powershell като приложения в сравнение с _Global_ колекции от средата за изпълнение на .NET. Чрез форсиране на GC.Collect() за да работи постоянно, можем да видим, че powershell инстанция управлява GC колекциите на сървъра. Използвайки тази група броячи на PerfMon, можем да проследим кои приложения изпълняват най-много колекции и оттам да продължим по-нататъшното проучване на проблема. В този случай простото спиране на скрипта PowerShell елиминира CLR_MANUAL_EVENT чака вътре в SQL Server и заявката продължава да обработва, докато не я спрем или не й позволим да върне милиарда реда резултати, които ще бъдат изведени от нея.
Заключение
Ако имате активни изчаквания за CLR_MANUAL_EVENT причинявайки забавяне на приложението, не приемайте автоматично, че проблемът съществува вътре в SQL Server. SQL Server използва събирането на боклука на ниво сървър (поне преди SQL Server 2017 CU4, където малки сървъри с под 2 GB RAM могат да използват събиране на боклук на ниво клиент, за да намалят използването на ресурси). Ако видите, че този проблем възниква в SQL Server, използвайте групата за броячи на .NET CLR памет в PerfMon и проверете дали друго приложение задвижва събирането на боклука в CLR и като резултат блокира CLR задачите вътрешно в SQL Server.