Baru-baru ini saya menerima pertanyaan email dari seseorang di komunitas tentang CLR_MANUAL_EVENT jenis tunggu; khususnya, cara memecahkan masalah dengan penantian ini tiba-tiba menjadi lazim untuk beban kerja yang ada yang sangat bergantung pada tipe data spasial dan kueri menggunakan metode spasial di SQL Server.
Sebagai konsultan, pertanyaan pertama saya hampir selalu, “Apa yang berubah?” Namun dalam kasus ini, seperti dalam banyak kasus, saya yakin bahwa tidak ada yang berubah dengan kode aplikasi atau pola beban kerja. Jadi pemberhentian pertama saya adalah menarik CLR_MANUAL_EVENT tunggu di Perpustakaan Jenis Tunggu SQLskills.com untuk melihat informasi lain apa yang telah kami kumpulkan tentang jenis tunggu ini, karena biasanya bukan penantian yang saya lihat bermasalah di SQL Server. Apa yang menurut saya sangat menarik adalah bagan/peta panas kejadian untuk jenis tunggu ini yang disediakan oleh SentryOne di bagian atas halaman:
Fakta bahwa tidak ada data yang dikumpulkan untuk jenis ini di seluruh bagian yang baik dari pelanggan mereka benar-benar menegaskan bagi saya bahwa ini bukan sesuatu yang biasanya menjadi masalah, jadi saya tertarik dengan fakta bahwa beban kerja khusus ini sekarang menunjukkan masalah dengan penantian ini. Saya tidak yakin ke mana harus pergi untuk menyelidiki masalah ini lebih lanjut, jadi saya membalas email yang mengatakan bahwa saya menyesal tidak dapat membantu lebih lanjut karena saya tidak tahu apa yang menyebabkan lusinan utas melakukan kueri spasial menjadi tiba-tiba mulai harus menunggu selama 2-4 detik pada satu waktu pada jenis menunggu ini.
Sehari kemudian, saya menerima email tindak lanjut yang baik dari orang yang mengajukan pertanyaan yang memberi tahu saya bahwa mereka telah menyelesaikan masalah. Memang tidak ada yang berubah dalam beban kerja aplikasi yang sebenarnya, tetapi ada perubahan pada lingkungan yang terjadi. Paket perangkat lunak pihak ketiga diinstal pada semua server di infrastruktur mereka oleh tim keamanan mereka, dan perangkat lunak ini mengumpulkan data pada interval lima menit dan menyebabkan pemrosesan pengumpulan sampah .NET berjalan sangat agresif dan "gila" sebagai mereka berkata. Berbekal informasi ini dan beberapa pengetahuan masa lalu saya tentang pengembangan .NET, saya memutuskan saya ingin bermain-main dengan ini beberapa dan melihat apakah saya dapat mereproduksi perilaku dan bagaimana kami dapat memecahkan masalah penyebab lebih lanjut.
Informasi Latar Belakang
Selama bertahun-tahun saya selalu mengikuti Blog PSSQL di MSDN, dan itu biasanya salah satu lokasi tujuan saya ketika saya ingat bahwa saya telah membaca tentang masalah yang terkait dengan SQL Server di beberapa titik di masa lalu tetapi saya bisa' t ingat semua spesifik.
Ada posting blog berjudul Tunggu tinggi di CLR_MANUAL_EVENT dan CLR_AUTO_EVENT oleh Jack Li dari tahun 2008 yang menjelaskan mengapa penantian ini dapat diabaikan dengan aman di sys.dm_os_wait_stats agregat DMV karena menunggu terjadi dalam kondisi normal, tetapi tidak membahas apa yang harus dilakukan jika waktu menunggu terlalu lama, atau apa yang dapat menyebabkannya terlihat di beberapa utas di sys.dm_os_waiting_tasks secara aktif.
Ada posting blog lain oleh Jack Li dari 2013 berjudul Masalah kinerja yang melibatkan pengumpulan sampah CLR dan pengaturan afinitas CPU SQL yang saya rujuk di kelas penyetelan kinerja IEPTO2 kami ketika saya berbicara tentang beberapa pertimbangan instance dan bagaimana .NET Garbage Collector (GC) dipicu oleh satu instance dapat memengaruhi instance lain di server yang sama.
GC di .NET ada untuk mengurangi penggunaan memori aplikasi yang menggunakan CLR dengan memungkinkan memori yang dialokasikan ke objek untuk dibersihkan secara otomatis, sehingga menghilangkan kebutuhan pengembang untuk menangani alokasi dan dealokasi memori secara manual ke tingkat yang diperlukan oleh kode yang tidak dikelola . Fungsionalitas GC didokumentasikan dalam Buku Daring jika Anda ingin tahu lebih banyak tentang cara kerjanya, tetapi spesifikasi di luar fakta bahwa koleksi dapat diblokir tidak penting untuk memecahkan masalah menunggu aktif di CLR_MANUAL_EVENT di SQL Server lebih lanjut.
Mendapatkan Akar Masalah
Dengan mengetahui bahwa pengumpulan sampah oleh .NET adalah penyebab terjadinya masalah, saya memutuskan untuk melakukan beberapa eksperimen menggunakan kueri spasial tunggal terhadap AdventureWorks2016 dan skrip PowerShell yang sangat sederhana untuk memanggil pengumpul sampah secara manual dalam satu lingkaran untuk melacak apa yang terjadi di sys.dm_os_waiting_tasks di dalam SQL Server untuk kueri:
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);
Kueri ini membandingkan semua alamat di Person.Address meja terhadap satu sama lain untuk menemukan alamat yang berada dalam jarak 100 meter dari alamat lain dalam tabel. Ini menciptakan tugas paralel yang berjalan lama di dalam SQL Server yang juga menghasilkan hasil Cartesian yang besar. Jika Anda memutuskan untuk mereproduksi perilaku ini sendiri, jangan berharap ini akan menyelesaikan atau mengembalikan hasil. Dengan menjalankan kueri, utas induk untuk tugas mulai menunggu di CXPACKET menunggu, dan kueri terus diproses selama beberapa menit. Namun, yang saya minati adalah apa yang terjadi ketika pengumpulan sampah terjadi di runtime CLR atau jika GC dipanggil, jadi saya menggunakan skrip PowerShell sederhana yang akan mengulang dan memaksa GC untuk berjalan secara manual.
CATATAN:INI BUKAN PRAKTIK YANG DIREKOMENDASIKAN DALAM KODE PRODUKSI KARENA BANYAK ALASAN!
while (1 -eq 1) {[System.GC]::Collect() }
Setelah jendela PowerShell berjalan, saya segera mulai melihat CLR_MANUAL_EVENT menunggu terjadi pada utas subtugas paralel (ditunjukkan di bawah, di mana exec_context_id lebih besar dari nol) di sys.dm_os_waiting_tasks :
Sekarang saya dapat memicu perilaku ini dan mulai menjadi jelas bahwa SQL Server tidak selalu menjadi masalah di sini dan mungkin hanya menjadi korban dari aktivitas lain, saya ingin tahu cara menggali lebih dalam dan menunjukkan akar penyebab masalah. . Di sinilah PerfMon berguna untuk melacak grup penghitung Memori .NET CLR untuk semua tugas di server.
Tangkapan layar ini telah diperkecil untuk menampilkan koleksi sqlservr dan powershell sebagai aplikasi dibandingkan dengan _Global_ koleksi oleh runtime .NET. Dengan memaksa GC.Collect() untuk berjalan terus-menerus kita dapat melihat bahwa powershell contoh mengemudi koleksi GC di server. Dengan menggunakan grup penghitung PerfMon ini, kami dapat melacak aplikasi apa yang melakukan koleksi paling banyak dan dari sana melanjutkan penyelidikan masalah lebih lanjut. Untuk kasus ini, cukup dengan menghentikan skrip PowerShell akan menghilangkan CLR_MANUAL_EVENT menunggu di dalam SQL Server dan kueri terus diproses hingga kami menghentikannya atau mengizinkannya mengembalikan miliaran baris hasil yang akan dihasilkan olehnya.
Kesimpulan
Jika Anda sudah aktif, tunggu CLR_MANUAL_EVENT menyebabkan pelambatan aplikasi, jangan secara otomatis berasumsi bahwa masalahnya ada di dalam SQL Server. SQL Server menggunakan pengumpulan sampah tingkat server (setidaknya sebelum SQL Server 2017 CU4 di mana server kecil dengan RAM di bawah 2 GB dapat menggunakan pengumpulan sampah tingkat klien untuk mengurangi penggunaan sumber daya). Jika Anda melihat masalah ini terjadi di SQL Server, gunakan grup penghitung memori .NET CLR di PerfMon dan periksa untuk melihat apakah aplikasi lain mendorong pengumpulan sampah di CLR dan sebagai hasilnya memblokir tugas CLR secara internal di SQL Server.