Keterlibatan konsultasi baru-baru ini difokuskan pada pemblokiran masalah di dalam SQL Server yang menyebabkan penundaan dalam memproses permintaan pengguna dari aplikasi. Saat kami mulai menggali masalah yang dialami, menjadi jelas bahwa dari sudut pandang SQL Server, masalahnya berkisar pada sesi dalam status Tidur yang menahan kunci di dalam Mesin. Ini bukan perilaku khas untuk SQL Server, jadi pikiran pertama saya adalah bahwa ada semacam cacat desain aplikasi yang membuat transaksi aktif pada sesi yang telah diatur ulang untuk penyatuan koneksi dalam aplikasi, tetapi ini dengan cepat terbukti tidak menjadi kasus karena kunci kemudian dilepaskan secara otomatis, hanya ada penundaan dalam hal ini terjadi. Jadi, kami harus menggali lebih dalam.
Memahami Status Sesi
Bergantung pada DMV mana yang Anda lihat untuk SQL Server, sesi dapat memiliki beberapa status berbeda. Status Sleeping berarti bahwa Mesin telah menyelesaikan perintah, segala sesuatu antara klien dan server telah menyelesaikan interaksi bijaksana, dan koneksi menunggu perintah berikutnya datang dari klien. Jika sesi tidur memiliki transaksi terbuka, itu selalu terkait dengan kode dan bukan SQL Server. Transaksi yang diadakan terbuka dapat dijelaskan oleh beberapa hal. Kemungkinan pertama adalah prosedur dengan transaksi eksplisit yang tidak mengaktifkan pengaturan XACT_ABORT dan kemudian time out tanpa aplikasi yang menangani pembersihan dengan benar seperti yang dijelaskan dalam posting yang sangat lama ini oleh tim CSS:
- Cara Kerjanya:Apa itu Sesi Perintah Tidur / Menunggu
Jika prosedur telah mengaktifkan pengaturan XACT_ABORT maka transaksi akan dibatalkan secara otomatis ketika waktu habis dan transaksi akan dibatalkan. SQL Server melakukan persis apa yang harus dilakukan di bawah standar ANSI dan untuk mempertahankan properti ACID dari perintah yang dijalankan. Batas waktu tidak terkait dengan SQL Server, ini disetel oleh klien .NET dan properti CommandTimeout, sehingga juga terkait kode dan bukan perilaku terkait SQL Engine. Ini adalah jenis masalah yang sama yang saya bicarakan di seri Acara yang Diperpanjang saya juga, di posting blog ini:
- Menggunakan Beberapa Target untuk Men-debug Transaksi Yatim Piatu
Namun, dalam kasus ini aplikasi tidak menggunakan prosedur tersimpan untuk mengakses database, dan semua kode dihasilkan oleh ORM. Pada titik ini penyelidikan beralih dari SQL Server dan lebih ke bagaimana aplikasi menggunakan ORM dan di mana transaksi akan dihasilkan oleh basis kode aplikasi.
Memahami Transaksi .NET
Sudah menjadi rahasia umum bahwa SQL Server membungkus modifikasi data apa pun dalam transaksi yang dilakukan secara otomatis kecuali opsi set IMPLICIT_TRANSACTIONS AKTIF untuk suatu sesi. Setelah memverifikasi bahwa ini tidak AKTIF untuk bagian mana pun dari kode mereka, cukup aman untuk mengasumsikan bahwa setiap transaksi yang tersisa setelah sesi Tidur adalah hasil dari transaksi eksplisit yang dibuka di suatu tempat selama eksekusi kode mereka. Sekarang tinggal memahami kapan, di mana, dan yang paling penting, mengapa tidak segera ditutup. Ini mengarah ke salah satu dari beberapa skenario berbeda yang harus kita cari di dalam kode tingkat aplikasi mereka:
- Aplikasi menggunakan TransactionScope() di sekitar operasi
- Aplikasi yang mendaftarkan SqlTransaction() pada koneksi
- Kode ORM yang membungkus panggilan tertentu dalam transaksi internal yang tidak dilakukan
Dokumentasi untuk TransactionScope dengan cepat mengesampingkan hal itu sebagai kemungkinan penyebabnya. Jika Anda gagal untuk Menyelesaikan cakupan transaksi, itu akan secara otomatis memutar kembali dan membatalkan transaksi saat dibuang, jadi kemungkinan besar ini tidak akan bertahan di seluruh pengaturan ulang koneksi. Demikian juga, objek SqlTransaction akan secara otomatis memutar kembali jika tidak dilakukan ketika koneksi diatur ulang untuk penyatuan koneksi, sehingga dengan cepat menjadi non-starter untuk masalah tersebut. Ini baru saja meninggalkan pembuatan kode ORM, setidaknya itulah yang saya pikirkan, dan akan sangat aneh untuk versi lama dari ORM yang sangat umum untuk menunjukkan jenis perilaku ini dari pengalaman saya, jadi kami harus menggali lebih jauh.
Dokumentasi untuk ORM yang mereka gunakan dengan jelas menyatakan bahwa ketika tindakan multi-entitas terjadi, itu dilakukan di dalam transaksi. Tindakan multi-entitas dapat berupa penyimpanan rekursif atau menyimpan koleksi entitas kembali ke database dari aplikasi, dan pengembang setuju bahwa jenis operasi ini terjadi di seluruh kode mereka, jadi ya, ORM harus menggunakan transaksi, tetapi mengapa mereka melakukannya? tiba-tiba menjadi masalah.
Akar Masalah
Pada titik ini kami mengambil langkah mundur dan mulai melakukan tinjauan holistik terhadap seluruh lingkungan menggunakan New Relic dan alat pemantauan lain yang tersedia ketika masalah pemblokiran muncul. Mulai menjadi jelas bahwa sesi tidur yang menahan kunci hanya terjadi ketika server Aplikasi IIS berada di bawah beban CPU yang ekstrem, tetapi itu sendiri tidak cukup untuk menjelaskan kelambatan yang terlihat dalam komit transaksi melepaskan kunci. Ternyata juga bahwa server aplikasi adalah mesin virtual yang berjalan pada host hypervisor yang overcommit, dan waktu tunggu CPU Ready untuk mereka sangat meningkat pada saat masalah pemblokiran berdasarkan nilai penjumlahan yang disediakan oleh Administrator VM.
Status Tidur akan terjadi dengan transaksi terbuka yang menahan kunci antara panggilan .SaveEntity dari objek yang diselesaikan dan komit terakhir dalam kode yang dihasilkan kode di belakang untuk objek. Jika server VM/App berada di bawah tekanan atau beban, maka ini dapat tertunda dan menyebabkan masalah dengan pemblokiran, tetapi masalahnya bukan di SQL Server, ia melakukan persis seperti yang seharusnya dalam lingkup transaksi. Masalahnya pada akhirnya adalah hasil dari keterlambatan dalam memproses titik komit sisi aplikasi. Menyelesaikan pengaturan waktu pernyataan dan peristiwa yang diselesaikan RPC dari Peristiwa yang Diperpanjang bersama dengan waktu peristiwa database_transaction_end menunjukkan penundaan bolak-balik dari tingkat aplikasi yang menutup transaksi pada koneksi terbuka. Dalam hal ini semua yang terlihat di SQL Server adalah korban dari server aplikasi yang kelebihan beban, dan host VM yang kelebihan beban. Memindahkan/membagi beban aplikasi di seluruh server dalam NLB atau konfigurasi seimbang beban perangkat keras menggunakan host yang tidak terlalu berkomitmen pada penggunaan CPU akan dengan cepat memulihkan komitmen langsung dari transaksi dan menghapus sesi Tidur yang menahan kunci di SQL Server.
Namun satu lagi contoh masalah lingkungan yang menyebabkan apa yang tampak seperti masalah pemblokiran biasa. Selalu bermanfaat untuk menyelidiki mengapa utas pemblokiran tidak dapat melepaskan kuncinya dengan cepat.