Bir Insert Cümlesi ile SQL Engine’ in Kararsız Hale Gelip Stop Olmasını Sağlayabilir miyiz?
Sonda söyleyeceğimi başta söyleyeyim, uygun şartlar sağlandığında evet mümkün! Aslında herhangi bir insert ifadesinden bahsetmiyoruz. Bahsi geçen ifade bir stored procedure içinde yer alıyor ve öncesinde-sonrasında bazı transactionları içeriyordu.
Yaşadığımız durumda sırasıyla aşağıdaki hataları log’a yazdıktan sonra database engine doğrudan stop duruma geçiyordu.
Could not find an entry for table or index with partition ID 8646911294668472320 in database 2. This error can occur if a stored procedure references a dropped table, or metadata is corrupted. Drop and re-create the stored procedure, or execute DBCC CHECKDB.
SQL Server must shut down in order to recover a database (database ID 2). The database is either a user database that could not be shut down or a system database. Restart SQL Server. If the database fails to recover after another startup, repair or restore the database.
SQL Server cannot accept new connections, because it is shutting down. The connection has been closed.
Legacy sistemlerimizden birinde yapılan bir tasarım hatası nedeniyle aşağıdaki gibi görselleştirebileceğimiz bir durum oluştuğunu farkettik.
Birbirini çağrıran stored procedure’ler içinde ve 4 adet nested transaction altında insert statement‘ı çalışmaktydı. Tabloda yaşanan bir sorundan dolayı insert yapılamayınca rollback başlıyor. Bu noktada Database Engine durumu handle edemeyince doğrudan stop oluyor.
Bu durum, onbinlerce tps’in döndüğü bir ortamda yaşanınca tespit etmek 3–4 saat kadar ortamın unstabil bir şekilde çalışmasına neden oldu ve bu zaman zarfında database engine 4 kez daha stop oldu.
Nested transactionlar içinde çalışan yüzlerce satır kod, transaction içinde yapılmaması gereken ve best practiclere uymayan temp table ddl işlemleri, temp tablo dml işlemleri, cursorlar, try-catch, deadlock priority, gibi sayılabilecek bir çok kötü kullanımı barındırıyor.
Rollback süreci başladığında Database Engine tempdb üzerinde aradığı birşeyleri bulamadığı için tutarsızlık olduğuna kanaat getirerek stop duruma geçiyordu. İlk aklımıza gelen transaction içinde kullanılan bir temp tablonun drop edilmesi nedeniyle engine’in rollback esnasında bu tabloyu bulamamasından kaynaklandığı yönündeydi.
Nitekim transaction içinde drop edilen temp tablolar mevcuttu. Fakat bunu düzelttiğimizde de sonucun değişmediğini gördük. Problemin yaşandığı version SQL2019(15.0.4322). Güncel versiyon olan SQL2022 (16.0.1000.6)’de denediğimizde kodun normal bir şekilde rollback olabilidiğini de gördük. Yani problem SQL 2019 un davranış şeklindeydi.
Tempdb, developerların ve userların can simidi olmasının yanında, hem internal operasyonlar hem de kullanıcının ihtiyaçları doğrultusunda tempdb’yi kullanabilmesi, bir noktada sunucu kaynaklarından bağımsız olarak tempdb’nin bir darboğaz noktası olmasına neden olabiliyor. Tempdb oriented (bu tabir bana ait) kod geliştirmenin olduğu ortamlarda bu darboğazı sıklıkla yaşamaktayız. Bu durumla mücadele için birçok özel yöntem geliştirmemiz gerekti. Çünkü yoğun bir ortamda tempdb’yi trace etmek pek mümkün ve güvenli değil.
Tempdb konusu ayrıca konuşulması gereken çok derin bir konu. Sadece şunu belirtmek gerek; Aşağıdaki kullanımların hepsi tempdb’yi doğrudan yada dolaylı olarak kullanmamız anlamına geliyor: Temp tables, Table Variables, DBCC Checkdb, Row Versions,Triggers, Statistics Updates, Online Index Operations,Hash Worktables, Table-Valued Functions, Cursors, Sorts, Spools.
Aslında engine’in ciddi bir yükü tempdb üzerinde dönüyor. Tempdb nihayetinde modifiye edilmiş bir user veritabanı ve diğerleri gibi tempdb’nin de ACID prensiplerine uygun hareket etmesi gerekiyor. Biz her ne kadar geçici işlemlerimiz için bu veritabanını kullanıyor olsak da arka planda işler öyle değil. Bu yüzden tempdb ile alakalı kullanımlarda özellikle dikkatli olmak gerekiyor.
Bu dipnotun ardından sorunumuza devam edecek olursak; daha önce yaşadığımız case’lerde genellikle re-pro etme imkanı bulunmuyordu. Bu yüzden açtığımız case’lerde Microsoft tarafından root cause konusunda net bulgulara ulaşılamıyordu. Bu case, ilk kez re-pro edebildiğimiz bir case oldu. Dolayısıyla detayları Microsoft’ a gönderme şansımız oldu. Makale hazırlandığı sırada microsoft tarafında henüz her hangi bir bulgu paylaşılmamıştı. Microsoft ‘un bir dönüşü olduğunda makaleye ekleyeceğiz.
Bizim tarafta bu duruma neden olabileceğini düşündüğümüz noktaları bir liste yaparak test ederken, sıra 2019 ile gelen tempdb metadata memory-optimized future’ una geldi. Bu future’ı enable yaptığınızda 3895 trace flag‘ı enable oluyor. SQL server tempdb de bulunan objelerin metadata bilgisini In-memory olarak tutmaya başlıyor. 2019 geçişinden sonra bu future’ın gerçekten faydasını gördüğümüzü söyleyebilirim. Tempdb contention darboğazlarını büyük oranda azaltabildik.
In-Memory tempdb metadata disable olduğunda kodun çalışabildiği ve rollback in tamamlanabildiğini gözlemledik. Yani bizim durumumuzda tempdb metadata memory-optimized future’ u SQL Engine’ in kararsız hale gelmesine ve kapanmasına neden oldu. Bu davranış şekli SQL Server 2022'de bulunmuyor. Yani sorgu In-memory tempdb metadata future’ı açık olsa da kapalı olsa da engine’ i durdurmadan rollback olabiliyor. Ek olarak belirtmek gerekir; oluşan sorun, başka request yada transactionlarla etkileşim sonucunda meydana gelen bir durum değil. Procedure, sunucuda tek başına çalıştığı zaman da aynı durum yaşandı.
Özetle; Tempdb avantajlarından yararlanırlarken ekstra dikkatli olmamız gerekiyor. Bir transaction içerisinde temp tablo ddl (create,alter,drop) ya da dml(Insert,Update,Delete) işlemlerini ise kesinlikle aklımızdan dahi geçirmemeliyiz. SQL Server Database Engine’nin güncel versiyondan bir önceki sürümünde bu şekilde davranması ise kritik bir bug gibi görünüyor. Dolayısıyla SQL Server 2019 üzerinde tempdb metadata memory-optimized enable yapıldığında böyle bir durum yaşama riskini göz önünde bulundurmalıyız.
Review için Emrah Erdoğan ve Selen Arslan’a teşekkürler…