İçeriğe geç

Database Connection Pool Tükenmesi: O Sorun Neden Başımıza Gelir?

Selamlar millet! Bugün aklıma takılan, bazen başıma gelmiş de olan bir konudan bahsedeceğim: Database Connection Pool Tükenmesi. Hani böyle her şey yolunda giderken, sistem çat diye durur ya, işte o anlarda aklımıza gelen ilk şeylerden biri bu oluyor sanırım. Bir anda uygulamanız kullanılamaz hale geliyor, kullanıcılar çıldırıyor, siz de ter içinde çözüm arıyorsunuz.

Şimdi öncelikle şu connection pool olayı neymiş, bir ona bakalım. Basitçe anlatmak gerekirse, her seferinde veritabanına bağlanıp işimiz bitince kapatmak yerine, belli sayıda bağlantıyı hazır tutan bir mekanizma bu. Yani hani siz de bir arkadaşınıza giderken kapıyı çalıp içeri giriyorsunuz ya, onun gibi. Hazır bir bağlantı var, kullanıyorsunuz, işiniz bitince geri veriyorsunuz. Bu sayede hem daha hızlı oluyorsunuz hem de sunucuya binen yük azalıyor. Ne güzel değil mi?

Ama işte, her güzelin bir kusuru oluyor. Bazen bu hazır tutulan bağlantılar bir anda tükeniveriyor. Hani bir bakıyorsunuz, ‘Error 500 Internal Server Error’ bildirimleri ekrana akıyor. İşte o an anlıyorsunuz ki, connection pool’unuz iflas etmiş. Neden olur peki bu?

En sık karşılaşılan sebep şu sanırım: Bağlantıları açık bırakmak. Yani bir işlem yapıyorsunuz, bağlantıyı alıyorsunuz ama işiniz bitse de geri vermiyorsunuz. Bu da zamanla o havuzdaki bütün bağlantıların dolmasına neden oluyor. Bir de hani şöyle bir durum var; bazı sorgularınız ya da işlemleriniz çok uzun sürüyor. Düşünün ki bir sorgunuz 10 dakika sürüyor. Bu sırada o bağlantı başka kimse tarafından kullanılamıyor.

Bazen de şöyle bir durumla karşılaşıyoruz, özellikle yoğun trafik alan uygulamalarda; gelen istek sayısı, havuzdaki bağlantı sayısından çok daha fazla oluyor. Yani aynı anda binlerce kişi girip bir şeyler yapmaya çalışıyor ama sizin elinizde sadece 50 tane bağlantı var. Hadi bakalım, buradan ekmek çıkmaz! Bu arada, veritabanının kendisinde de bazı sınırlamalar olabilir, onu da unutmamak lazım.

Peki, bu işin içinden nasıl sıyrılırız? İlk akla gelen çözüm, connection pool’unuzun boyutunu artırmak. Hani daha çok bağlantı isteyin, daha çok hazır bulunsun. Tabi bu her zaman en iyi çözüm olmayabilir. Bazen sunucu kaynaklarını da zorlayabiliriz. O yüzden bu ayarı yaparken dikkatli olmak lazım. Bir de şöyle bir şey var; kullandığınız bağlantıları gerçekten işiniz bittiğinde geri verdiğinizden emin olun. Özellikle uzun süren işlemleri kontrol etmek lazım. Acaba orada bir yerde takılıp kalıyor mu diye bakmak lazım. Hani bir de şu var, bazı kütüphaneler veya ORM’ler (Object-Relational Mapper) var ya, onlar da bazen bağlantıları doğru yönetmeyebiliyor. Mesela Dapper kullanıyorsanız, bağlantıyı manuel olarak açıp kapatmanız gerekebilir, yoksa açık kalabilir. İşte ben de kendi C# projelerimde bazen bunu unutabiliyorum. Sonra bir bakıyorum, sistem yavaşlamış, bir şeyler olmuş. Neyse efendim, kendi koduma da bakmam lazım yani 🙂

Bu arada, veritabanı sorgularınızı da optimize etmek önemli. Çok yavaş çalışan, gereksiz yere çok veri çeken sorgularınız varsa, connection pool’u daha çabuk tüketirsiniz. Bunun için de bazen sorguları gözden geçirmek, indeksleri kontrol etmek iyi olabilir. Hani bir de şöyle bir durum var, veritabanınızın kendisinin de performansını artıracak şeyler yapabilirsiniz. Mesela sorgu önbellekleme gibi.

Şimdi gelelim işin kod kısmına. Basit bir .NET Core örneği üzerinden gidelim. Şöyle bir senaryo düşünelim: Bir tane basit API endpoint’imiz var ve bu endpoint her çağrıldığında veritabanından bir şeyler çekiyor. Eğer biz bu bağlantıyı doğru kapatmazsak ne olur?

Öncelikle, bağlantıyı doğru yönetmezsek ne olur ona bakalım. Şöyle bir kod yazdığınızı düşünün:

// YANLIŞ KOD ÖRNEĞİ

public async Task<IActionResult> GetVeri()

{

using (var connection = new SqlConnection("connection_string"))

{

await connection.OpenAsync();

// Buradaki sorgu çok uzun sürüyor veya hata veriyor ve bağlantı kapanmıyor

var result = await connection.QueryAsync("SELECT * FROM UzunSurenTablo");

// Bağlantı burada açık kalır, explicit olarak kapatılmazsa

return Ok(result);

} // using bloğu bittiğinde connection.Dispose() çağrılır, bu da bağlantıyı kapatır.

// Fakat eğer hata olursa veya işlem uzun sürerse, belki de kapanmayabilir veya doğru zamanda kapanmayabilir.

}

Şimdi burada ‘using’ bloğu fena değil, bağlantıyı Dispose ettiğinde kapatır ama ya sorgu hata verirse? Ya da şöyle bir şey yapsak, bağlantıyı alıp bir yere atsak ve sonra unutsak? Bu daha kötü olurdu sanırım.

Doğrusu ne olmalı peki? Bağlantıları gerçekten ihtiyacımız olduğunda alıp, işimiz bittiğinde en kısa sürede geri vermek. Genellikle ORM’ler veya data access kütüphaneleri bunu bizim için halleder ama yine de dikkat etmekte fayda var. Mesela SqlConnection’ı using bloğunda kullanmak iyi bir başlangıç. Ama asıl mesele, o bloğun içindeki işlemin ne kadar sürdüğü ve hata durumlarında ne olduğu.

Şimdi daha doğru bir yaklaşım nasıl olur ona bakalım. Bu örnekte ‘using’ bloğu hala yerinde ama asıl odak noktamız sorgunun kendisi ve bağlantı yönetimi. Genellikle bir connection pool’u olan veritabanı sağlayıcısı (PostgreSQL, SQL Server vb.) kullanırken, kütüphaneler (örneğin Dapper veya Entity Framework) sizin için bağlantıyı yönetir. Yani siz `connection.Open()` dediğinizde, pool’dan boş bir bağlantı alır, işiniz bitince `connection.Close()` dediğinizde veya `Dispose()` ettiğinizde pool’a geri verir. Asıl sorun, bu süreçte bir hata oluştuğunda veya bağlantı çok uzun süre meşgul kaldığında ortaya çıkar.

Yani aslında, kodumuzda yapmamız gereken şey, bu bağlantıları mümkün olduğunca kısa süre meşgul tutmak. Belki de sorgularımızı daha verimli hale getirmek ilk adım olmalı. Hani mesela, sadece ihtiyacımız olan kolonları çekmek gibi. Ya da gereksiz yere büyük veri setlerini uygulamaya aktarmamak.

İşte daha sağlam bir yaklaşım böyle olabilir:

// DOĞRU KOD ÖRNEĞİ

public async Task<IActionResult> GetVeriOptimize()

{

// Bağlantı havuzundan bir bağlantı alınır.

using (var connection = await _connectionFactory.CreateConnectionAsync()) // Kendi factory'niz veya DI ile gelen yapı

{

// Sorguyu optimize et, sadece ihtiyacın olanı çek

var query = "SELECT Id, Ad, Soyad FROM Kullanicilar WHERE Aktif = 1";

// Sorgunun uzun sürmemesi için dikkat et

var result = await connection.QueryAsync<Kullanici>(query);

// Bağlantı using bloğundan çıktığında otomatik olarak kapatılıp havuza iade edilir

return Ok(result);

}

}

Burada `_connectionFactory.CreateConnectionAsync()` gibi bir yapı kullanmak, connection pool’u daha iyi yönetmemizi sağlar. Yani kütüphane sizin için pool’dan uygun bir bağlantı bulur, işiniz bitince de geri verir. Önemli olan, `using` bloğunun içini olabildiğince kısa tutmak ve verimli sorgular yazmak. Bu arada, veritabanı performansını artırmak için indeksleme de çok önemli değil mi?

Sonuç olarak, Database Connection Pool Tükenmesi can sıkıcı bir sorun olsa da, doğru önlemlerle başa çıkılabilir. Bağlantıları doğru yönetmek, sorguları optimize etmek ve havuz boyutunu akıllıca ayarlamak bu işin anahtarları sanırım. Hani bazen de sadece sistemi yeniden başlatmak bile geçici bir çözüm olabiliyor ama bu kalıcı bir çözüm değil tabii ki. 🙂 Bu arada, performans izleme araçlarını kullanmak da sorunun kaynağını bulmada çok yardımcı oluyor. Ne güzel değil mi?

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.