MS-SQL

SQL Server ve ASP.Net ile Aşırı Yük Taşıyan Web Siteleri ve Sunucu Problemleri

“SQL  Server ve ASP.Net ile aşırı yük taşıyan web siteleri ve sunucu problemleri” evet, konumuz tam olarak bu. Bulunduğum ortamlarda birçok sunucu problemine tanık oldum. Hemen söylemeliyim ki bunların hemen hepsi yanlış yapılandırılmış sistemler veya geliştirilmiş kodlar yüzünden ortaya çıkmış sorunlardı. Şunu unutmamak gerekir ki performans açısından yetersiz yazılmış birçok kod veya yanlış yapılandırma uygulamanın üzerindeki yük hafif olduğunda kendini göstermeyecek ve uygulamanızdaki kullanım arttıkça ortaya çıkacaktır. Çoğu zaman da bazı problemler diğerlerini gizlediğinden problemlerin tümü bir kerede değil de su yüzünde olanlar fark edilecektir. Bundan dolayı bir uygulamanın kaldıracağı yükün iyi analiz edilmesi ve buna uygun yapılandırılması ve geliştirilmesi önemli bir konudur. Uygulama hayata geçmeden önce bu konu için katlanılan maliyet hayata geçtikten sonra yaşanan performans problemleri yüzünden kaybedilen itibarın maliyetinden daha az olacaktır.

Bu yazımda ben, yaşamış olduğum tecrübelerden yola çıkarak yoğun kullanıma sahip, MS-SQL Server kullanan bir ASP.Net uygulamasında dikkat edilmesi gereken birkaç noktayı göz önüne getirmeye çalışacağım.

Konuları incelerken hangi şartlar altında oluşabileceklerine de yer vermeye çalışacağım. Çünkü bir sistemde karşılaşılan sorunlar aynı olurken nedenleri farklı olabilmektedir. Sizin karşılaştığınız sorunun çözümü tam olarak burada anlatılan olamayabilir. Bundan dolayı yaptığım anlatımları her açıdan incelemeniz önem taşıyor. Burada anlattığım bulguların hepsi MS-SQL Server 2000 kullanan sistemlerde karşılaştığım durumlardır. Diğer sürümlerde konuları ayrıca incelemenizi tavsiye ederim.

Otomatik Büyüme (Auto Grow) Boyutu

MS-SQL Server’da verileriniz veri dosyalarında tutulmaktadır. Her veri tabanı kullanıldıkça (veri eklemeleri oldukça) bu veri dosyalarını büyütmeye (diskinizde bu dosyalar için yer ayırmaya) ihtiyaç duyar. MS-SQL Server’da bu büyüme varsayılan olarak o anki veri dosyası boyutunun %10’u kadardır. Diğer bir taraftan büyük ve yoğun (sık) veri eklemelerinin olduğu veri tabanlarında otomatik olarak başlayan bu büyüme işlemi sorunlara yol açabilmektedir. Bu büyüme işlemi sırasında veri tabanı isteklere cevap veremez duruma düşerek istemci uygulamanın hataya girmesine neden olabilmektedir.

Bir sistemde aşağıdaki şekilde hatalar alınıyorsa muhtemelen bu şekilde bir durum meydana gelmektedir.

“Autogrow of file ‘Database File Name’ in database ‘Database Name’ took Number milliseconds. Consider using ALTER DATABASE to set a smaller FILEGROWTH for this file.”

Bu noktada bu mesajları nasıl fark edeceğinizi merak ediyorsunuzdur. Bu tip hataları olay görüntüleyicinizden (Event Viewer) veya SQL Server işlem kayıtlarından (Log) takip edebilirsiniz. Bu kısımlar çoğu zaman unutulsa da aslında birçok sorunun nedenlerini en iyi takip edebileceğimiz yerlerdir ve devamlı kontrol altında tutulmalıdırlar.

Konumuza geri dönersek, bu tip bir sorunla karşılaşıldığında yapılması gereken otomatik büyüme (Auto Grow) işlemini kontrol altına almaktır. Bu konuyu daha ayrıntılı olarak “SQL Server ve Otomatik Büyüme (AutoGrow) İşleminin Uygulamanıza Etkileri” başlıklı yazımda inceleyebilirsiniz.

Geçici Tablo (Temporary Table) Kullanımı

Geçici tablolar (temporary table) çoğu zaman farklı tablolardan veriler üzerinde işlem yapmaya ihtiyaç duyduğumuz prosedürlerimizde yardımımıza koşmuştur. Ancak her geçici tablo (temporary table) SQL Server’ı “tempdb”  üzerinde bir tablo oluşturmaya zorlayacaktır. Buda bir disk aktivitesine ve performans ihtiyacına neden olacaktır. Eğer prosedürünüz yoğun kullanılan bir prosedür ise bu durum sisteminizde performans zayıflığı oluşturan bir nokta haline gelecektir. Bu noktada belirtmem gerekiyor ki eğer hafıza (Memory) durumu elverişli ise SQL Server geçici tabloları hafıza (Memory) içinde oluşturup kullanacaktır. Ancak burada bahsettiğimiz sistemin yoğun kullanımda olan ve yüklü hacimde veriye sahip olan bir sistem olduğunu hatırlatmak isterim. Bundan dolayı bu tip kullanımlar disk kullanımını arttırabilmektedir.

Geçici tablo (temporary table) kullanımında bir diğer önemli nokta ise “global temporary table” kullanımında ortaya çıkmaktadır. Bildiğiniz gibi bir geçici tabloyu (temporary table) iki ‘#’ işareti ile oluşturursanız bu bir “global temporary table“ olacaktır ve SQL Server’daki tüm bağlantılar için görünür olacaktır.

Bu tablolara genelde aynı veritabanı üzerideki birbirinden bağımsız işlemlerin ortak bir veri kümesi üzerinde işlem yapması gerektiğinde ihtiyaç duyulur. Bu nedenle de aynı tabloya farklı işlemler yazma ve okuma işlemi yapar. Bu noktada unutulan gerçek ise tablo üzerinde oluşacak Lock’lardır.

Bu tipte çalışan bir sisteminiz varsa bu sistem yoğun bir kullanıma sahip olduğunda sisteminizde engelleme (blocking) problemleri ile karşılaşmanız mümkündür. Buda uygulamanızın oldukça yavaşlamasına veya isteklere gereken sürede cevap verememesine yol açacaktır.

Yeri gelmişken belirtmek isterim, tablo değişkenleri geçici tablolara alternatif olarak görülebilir. Zannedilenin aksine tablo değişkenleri de (Memory Table) gerektiğinde disk kullanacaktır. Tablo değişkenleri (Table Variable) hakkında merak edilenler için KB305977 görülebilir.

Bu nedenlerden dolayı geçici tablo (temporary table) kullanımı dikkat edilmesi ve kullanım yeri iyi seçilmesi gereken bir tekniktir.
“Session” Kullanımı

Sisteminizin performansını olumsuz yönde etkileyebilecek bir diğer nokta ise yanlış “Session” kullanımıdır.

Bildiğiniz gibi “Session” bir web uygulamasında ziyaretçinin oturumu boyunca geçerli olan verileri barındırmak için kullanılır. Oturum (Session) nesnesi her kullanıcı için oluşturulur ve oturum zaman aşımı (Session time-out) boyunca canlı kalır. Bu da sisteminizin birim zamandaki ziyaretçi sayısı arttıkça daha fazla hafıza (Memory) ihtiyacına neden olur.  Bu durum genelde oturumun (Session) “In Proccess” tutulduğu sistemlerde sorunlara neden olabilir.

Bu durumdan kaçınmak için oturum zaman aşımı (Session time-out) sürenizi olabildiğince kısa tutmalısınız. Buna ek olarak, oturum (Session) nesnesinde gerekli olmayan, büyük verilerin saklanılmasından kaçınılmalıdır.

Genelde yapılan hata bir kullanıcıya ait tüm verinin veri tabanından alınması ve oturum (Session) nesnesinde saklanılmasıdır. Bunun yerine oturum (Session) nesnesinde kullanıcıya ait belirleyici (ID) değer tutulmalı ve gerektiğinde kullanıcıya ait veri bu değer ile veri tabanından sorgulanmalıdır. Gerekli olan verinin kullanım sıklığına göre az kullanılan verilere bu yöntemle ulaşılması daha makul bir çözüm olacaktır.

Veri Tabanı Bağlantılarının (Database Connection) Kullanımı

Web uygulamaları çok kullanıcılı uygulamalar olması bakımında çok fazla veritabanı bağlantısı kullanmaya ihtiyaç duyabilirler. Bir IIS uygulamasında, varsayılan ayarlarda veritabanı bağlantıları (Database Connection) bir bağlantı havuzundan (Connection Pool) kullanılır. Yani her bağlantıya ihtiyaç duyduğunuzda yeni bir bağlantı oluşturulmaz. Bunun yerine bağlantı havuzundan (Connection Pool) ihtiyacınız ile aynı bağlantı cümlesine (Connection String) sahip, kullanılmayan bağlantı size tahsis edilir. Burada altı çizilmesi gereken kelime “kullanılmayan” kelimesidir. Yani sizin işleminiz o bağlantı ile işinizi bitirip bağlantıyı kapatana kadar bağlantıyı başka bir işlem kullanamayacaktır.

Bu bakış açısı ile eğer sisteminizde bir bağlantıyı açıp uzun süre kapatmayan işlemleriniz (kod kümeleriniz) varsa bu işlemler sisteminizde performans kayıplarına yol açabilir. Nasıl mı? Yoğun kullanımlarda birim zamanda ihtiyaç duyulan bağlantı sayısı artacaktır. Veritabanı işlemi yapan kodunuzun ise bu birim zaman içinde işini bitiremediğini düşünelim. Bundan dolayı da veritabanı bağlantısı (Connection) serbest kalmayacaktır. Bunun sonucu olarak bağlantı havuzundaki (Connection Pool) bağlantılar bittiğinde her yeni bağlantı isteği bir bağlantının serbest kalmasını bekleyecektir. İşte bu bekleme süreleri sisteminizin yavaşlamasına, isteklere cevap vermemesine neden olacaktır.

Bundan kaçınmak için bir veri tabanı bağlantısı açıldığında bu bağlantı en kısa sürede kapatılmalı ve bağlantının açıldığı ve kapatıldığı kod satırları arasına gerekli olmayan başka hiçbir işlem yapılmamalıdır.

Bunun yanında bu gibi bir durumu gözlemleyebilmek için SQL Server’ın “Bağlı Kullanıcılar (Connected Users)” sayacını izlemekte fayda vardır.

“Cursor” Kullanımı

“Cursor” kullanımı çoğu zaman prosedürlerimizi oluştururken veriler ile karmaşık işlemler yapmamız gerektiğinde başvurduğumuz bir tekniktir. Ancak SQL Server’ın “Cursor” için “tempdb” kullandığını unutmamak gerekir. “Cursor” bu özelliklerinden dolayı masraflı bir tekniktir ve yoğun kullanılan bir prosedür içinde kullanılmışsa sorunlara yol açabilir.

Ayrıca bir “Cursor” açıldığında SQL Server’ın ilgili tabloda “Lock” kullandığını göz önüne almak gerekir. Aslında SQL Server’ın bu davranışını yönetmek mümkündür. Ancak ne yaptığımızı bilmiyorsak veri tutarlılığına (Data Integrity) zarar verebileceğimizi unutmamak gerekir.
 

Veri Ön Bellekleri (Data Caching) Kullanımı

Web uygulamaları çok kullanıcılı uygulamalardır ve günümüz Web uygulamaları yuğun biçimde veritabanı kullanan oldukça dinamik uygulamalardır. Bu nedenden dolayı uygulamamızdaki kulanım artışı veri tabanımız üzerinde de yük artışı demek olacaktır. Zorlanan bir veri tabanı ise uygulamamızı oldukça yavaşlatacaktır. Uygulamamızın sağlığı açısından veri tabanı üzerindeki yükü olabildiğince azaltmaya çalışmalı başka bir deyişle veri tabanı kaynağımızı tutumlu kullanmalıyız.

Bunun içinde başvurabileceğimiz yöntem verilerimizi uygulamamızın hafızasında tutmak ve buradan kullanma ve veri tabanı tarafında bir değişiklik olduğunda uygulamamız tarafındaki veriyi veri tarafından tekrar almaktır. Veri ön belleklerinin (Data Caching) en büyük faydası veri tabanına giden istekleri çok büyük ölçüde azaltmasıdır.

Bir örnekle açıklamaya çalışalım. Bir sınıftaki öğrenci listesini veri tabanından okuyarak gösteren bir sayfanız olsun. Bu sayfayı birim zamanda 1000 kişinin ziyaret ettiğini düşünün. Bu veri tabanına 1000 istek gideceği anlamına gelir. Ancak veri ön belleği (Data Caching) kullanırsak veri tabanına istek yapacak olan işlem sadece veri önbelleğimizi güncelleyen işlem olacaktır ve oda veri değişene kadar veri tabanına sadece tek bir istek yapacaktır. Sayfamız ise veriyi veri ön belleğinden isteyecektir. Veri ön belleğimiz ise uygulamamızın hafızasında barındırıldığından bu işlem veri tabanı kullanılmadan çok hızlı bir biçimde tamamlanacaktır. Bu sayede sayfamız veri tabanına ihtiyaç duymadığından daha hızlı çalışacak, diğer taratanda veri tabanımız daha az yorulacağından gerektiğinde isteklerimize daha hızlı cevap verebilecektir.

Veri Tabanı Tablo İndeksleri ve İndeks Bakım Planı

Sizinde bildiğiniz gibi tablo indeksleri (Table Index)  uygulamamızın performansını büyük ölçüde arttıran ve bir veritabanı tasarımında unutulmaması gereken elemanlardır. Öte yandan tablo indeksleri (Table Index) uygulamamıza fayda verdiği gibi zararda verebilir.  Aslında bunu isteyerek yapmazlar. Yapıları gereği zaman içinde SQL Server’ı yoran elemanlar haline gelebilirler. Lafı fazla uzatmadan ne demek istediğimi anlatayım.

MS SQL Server’da tablo indeksleri (Table Index) sayfalar (Page) halinde tutulur. Her ekleme ve silme (Insert, Delete, Update)  işlemi indeks sayfalarının  (Index Page) değişmesine neden olur. Tablolarda yapılan değişiklikler (Insert, Delete, Update) sonucunda indeks sayfalarında (Index Page) bölünmeler (Page Split) olur. SQL Server “Fill Factor” adı verilen değeri kullanarak her sayfanın sonunda, “Page Split” olayını ertelemek veya daha az yapmak için boşluklar bırakır. Çünkü “Page Split” olayı yüksek derecede disk aktivitesi gerektiren bir olaydır ve SQL Server bu olayı çok fazla yapmak istemez. Ancak zaman içinde sayfa sonarındaki boşluklar dolar ve “Page Split” olayı meydana gelmeye başlar.

Bir tablo üzerinde indekslerin durumunu görmek için “DBCC SHOWCONTING” komutu kullanılabilir.  Örnek olarak, aşağıdaki komut  “Employees” tablosundaki tüm indeksler için istatistikleri gösterir.

USE Northwind
GO
DBCC SHOWCONTIG (Employees)
GO

Ayrıca “DBCC DBREINDEX” komutu ile indeksler yeniden düzenlenebilir ve “Fill Factor” değeri yeniden ayarlanabilir. Yeri gelmişken belirtmekte fayda var; “Fill Factor” değeri indeks (Index) oluşturulduktan sonra değiştirilemez. Bunun için “DBREINDEX” ile indeksi (Index) tekrar oluşturmalısınız. Eğer sisteminiz “DBREINDEX” komutunun oluşturacağı performans kaybını göze alamayacak kadar yoğunsa bunun yerine bir “ONLINE” komut olan “DBCC DBDEFRAG” kullanılabilir.

En makul çözüm ise sisteminizin ihtiyaçlarını göz önüne alarak bir indeks bakım planı (Index Maintenance Plan) oluşturmak olacaktır. Bunun için SQL Server üzerinde “Database Maintenance Plan” bölümü kullanılabilir.

Sonuç olarak, yoğun sistemlerde tablo indeksleri (Table Index)  iyice düşünülerek doğru bir biçimde oluşturulması veya zaman içinde yeniden düzenlenmesi gereken elemanlardır. Bunun için ise büyük tablolardaki indeksler takip edilmeli, “Fill Factor” değeri doğru bir biçimde belirlenmeli ve bakım planları oluşturulmalıdır. Burada belirtmek isterim ki, “Fill Factor” değeri hassas bir değerdir ve doğru değeri seçmek çok önemlidir.

“Stored Procedure” Kullanımı

Ağır yük taşıyan uygulamalarda “Stored Procedure” kullanmanın performans açısından ne kadar faydalı olduğuna değinmeden geçemezdim. Çünkü yakın zamanda bu sayede bir uygulamanın hayatının kurtulduğunu gördüm.

Eğer uygulamanızda karışık iş süreçlerini SQL üzerinde gerçekleştiriyorsanız ve bu kodlar sıklıkla kullanılan bir noktada ise bunları “Stored Procedure” haline getirmek gerekmektedir.

Bazen uzun ve karmaşık SQL cümlelerini uygulamamız içinde bir “String” olarak oluşturup çalıştırmak bir prosedür oluşturmaktan daha kolay olur. Böyle bir durumda, bu derece karışık bir SQL cümlesinin veya cümlelerinin  “Stored Procedure” olarak değil de “String” olarak oluşturulup çalıştırılması SQL Server’ı çok daha fazla yoracaktır.

Bize kolaylık getiren bir diğer yöntemde SQL cümlemizi “Stored Procedure” içinde dinamik oluşturmak ve “EXEC” konutu ile alıştırmak. Bu yöntem de önceki yöntem gibi SQL Server’ı yoran bir yöntem olacaktır.

Neden bu yöntemler SQL Server’ı yorar?

Bu şekilde dinamik oluşturulan SQL sorguları SQL Server’ın “Execution Plan” kullanmasına engel olur.  SQL Server her sorgu için “Query Optimizer” yardımı ile en ideal çalıştırma yöntemini seçer. Basit olarak açıklamak gerekirse; bu ideal çalışma planı (Execution Plan) içinde hangi tablolarda hangi indekslerin (Table Index) kullanılacağı, tablolardaki kayıtlar arasında dolaşırken nasıl döngüler kullanacağı… gibi yöntemleri belirler ve bu planları bir önbellekte (Cache) tutar. Yani bu belirlemeyi tekrar yapmaz. Ancak dinamik sorgular için bunu yapamaz. Çünkü sorgu her defasında değişir ve SQL Server, her defasında en ideal yolu bulmak için analizi tekrar yapar. Buda SQL Server’ı çok fazla yorar ve çok ağır kullanımlar altında inanılmaz bir CPU yükü getirebilir.

Bunlardan kaçınmak için bu tipte dinamik sorgularınızı “Stored Procedure” hailen getirmelisiniz. Bazen sorgular, bir prosedürde yazılamayacak kadar karmaşık olabilir. Zaten bu tip dinamik sorgular oluşturulmasının asıl nedeni çoğu zaman budur. Bu durumda sorgularınızı dinamik olarak çalıştırmak yerine birkaç farklı prosedüre bölmeyi ve şart  (IF) komutlarını kullanarak ana sorgunuzu organize etmeyi deneyebilirsiniz. Tabi elde ettiğiniz sonucu bir ön bellekte (Cache) tutup buradan kullanıp sadece belirli sürelerde veri tabanına başvurarak yenilemekte bu sisteme yardımcı olacak bir yöntemdir.  Bunun için “Veri Ön Bellekleri (Data Caching) Kullanımı” başlığı altındaki konuya bakabilirsiniz.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir