Database Connection Pooling: Timeout ve Pool Starvation Sorunlarını Çözme
Veritabanı bağlantıları, uygulamanızın en değerli ve en sınırlı kaynaklarından biridir. Her yeni bağlantı açmak, TCP el sıkışması (handshake), kimlik doğrulama (authentication) ve bellek tahsisi gibi maliyetli işlemleri beraberinde getirir. Bu maliyeti azaltmak için .NET, Connection Pooling (Bağlantı Havuzu) mekanizmasını kullanır. Uygulama kapandığında bağlantılar gerçekten kapatılmaz, bir havuza (pool) geri gönderilir ve sonraki isteklerde tekrar kullanılır.
Ancak, yanlış kullanıldığında bu havuz, uygulamanızın en büyük kabusuna dönüşebilir. Timeout, Pool Starvation (Havuz Tükenmesi) ve Connection Leak (Bağlantı Sızıntısı) gibi sorunlar, uygulamanızın tamamen donmasına veya kullanılamaz hale gelmesine neden olabilir. Bu yazıda, connection pool'ların nasıl çalıştığını, en sık karşılaşılan sorunları ve bu sorunları çözmek için uygulayabileceğiniz stratejileri derinlemesine inceleyeceğiz.
1. Connection Pool Nasıl Çalışır?
Bir uygulama ilk kez bir veritabanı bağlantısı açtığında (örn. SqlConnection.Open()), .NET bir bağlantı havuzu oluşturur. Bu havuz, belirli bir connection string ile ilişkilendirilir. Her farklı connection string için ayrı bir havuz oluşturulur.
Akış:
-
Uygulama
SqlConnection.Open()çağrısı yapar. -
.NET, ilgili connection string'e ait havuzda boş bir bağlantı olup olmadığını kontrol eder.
-
Varsa (Pool Hit): Havuzdaki mevcut bağlantı uygulamaya verilir. (Çok hızlıdır).
-
Yoksa (Pool Miss): Veritabanına yeni bir fiziksel bağlantı açılır ve havuza eklenir.
-
Uygulama
Dispose()veyaClose()çağrısı yaptığında, bağlantı kapatılmaz, havuza geri gönderilir (released).
Bu mekanizma sayesinde, bağlantı açma maliyeti yalnızca ilk kez veya havuz boşaldığında ödenir.
2. En Yaygın Sorunlar
A. Pool Starvation (Havuz Tükenmesi)
Havuzdaki tüm bağlantılar kullanımdayken yeni bir bağlantı talebi geldiğinde, havuz tükenir (pool starvation). Bu durumda, uygulama yeni bir bağlantı açmak için ya havuzda bir bağlantı boşalana kadar bekler (timeout) ya da bir hata fırlatır.
Nedenleri:
-
Bağlantıların Kapatılmaması (Leak): En yaygın nedendir.
Dispose()veyaClose()çağrılmazsa, bağlantı havuza geri dönmez ve havuz tükenir. -
Uzun Süren Sorgular: Bir bağlantı üzerinde çok uzun süren sorgular (ör. 10 dakika) havuzda yer işgal eder.
-
Yüksek Eşzamanlılık (Concurrency): Havuzun boyutu, eşzamanlı istek sayısından küçükse (varsayılan genelde 100'dür), havuz hızla tükenir.
B. Connection Timeout (Bağlantı Zaman Aşımı)
Bir bağlantı talebi, havuzda boş bağlantı bulunamadığı için beklemeye başlar. Varsayılan bekleme süresi genelde 15 saniyedir. Bu süre dolduğunda, SqlException fırlatılır:
"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool."
C. Connection Leak (Bağlantı Sızıntısı)
Bağlantıların havuza geri gönderilmemesi durumudur. Zamanla havuzdaki tüm bağlantılar "kullanımda" kalır ve uygulama çöker.
3. Çözüm Stratejileri
A. Bağlantıları Doğru Şekilde Kapatmak (En Önemlisi)
✅ Doğru Kullanım - using Bloğu:
En güvenli yöntem, bağlantıyı bir using bloğu içinde oluşturmaktır. Blok sona erdiğinde, Dispose() otomatik çağrılır.
csharp
using (var connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
// Sorgu çalıştır...
} // Burada connection.Dispose() otomatik çağrılır, bağlantı havuza döner.
✅ Doğru Kullanım - await using (C# 8.0+):
Asenkron metotlarda await using kullanarak da aynı güvenceyi sağlayabilirsiniz.
csharp
await using var connection = new SqlConnection(connectionString); await connection.OpenAsync(); // Sorgu çalıştır... // Burada otomatik olarak dispose olur.
✅ Daha Güvenli - Dapper ile:
Dapper'ın QueryAsync metodu, bağlantıyı otomatik yönetir. Bağlantıyı siz açıp kapatmazsanız, Dapper sizin için açar ve kapatır.
csharp
// Bu kullanımda bağlantıyı manuel açmanıza gerek yok!
using (var connection = new SqlConnection(connectionString))
{
var result = await connection.QueryAsync<Product>("SELECT * FROM Products");
} // Bağlantı otomatik havuza döner.
❌ YANLIŞ Kullanım - Manually Dispose Etmemek:
csharp
var connection = new SqlConnection(connectionString); await connection.OpenAsync(); // Sorgu çalıştır... // connection.Dispose() çağrılmazsa bağlantı havuza dönmez!
B. Max Pool Size Ayarları
Havuzun maksimum boyutunu artırmak, geçici bir çözüm olabilir. Connection string'ine Max Pool Size ekleyerek havuz boyutunu artırabilirsiniz.
csharp
// Varsayılan Max Pool Size = 100 "Server=localhost;Database=MyDb;User Id=sa;Password=123;Max Pool Size=200;"
Dikkat: Max Pool Size'ı artırmak, sorunu çözmez. Sadece zaman kazandırır. Asıl sorun (bağlantı sızıntısı veya yüksek eşzamanlılık) devam ediyorsa, havuz yine tükenecektir.
C. Bağlantı Bekleme Süresi (Connection Timeout)
Varsayılan bağlantı bekleme süresi 15 saniyedir. Bu süreyi artırabilirsiniz, ancak bu da geçici bir çözümdür.
csharp
"Server=localhost;Database=MyDb;User Id=sa;Password=123;Connection Timeout=30;"
D. Havuzu Temizleme (Clear Pool)
Sorunlu bir bağlantı havuzunu temizlemek için SqlConnection.ClearPool() veya SqlConnection.ClearAllPools() metotlarını kullanabilirsiniz. Bu, tüm bağlantıları kapatır ve havuzu sıfırlar.
csharp
SqlConnection.ClearAllPools(); // Tüm havuzları temizler (dikkatli kullanın!)
E. Bağlantı Havuzunu İzleme (Monitoring)
Sorunları tespit etmek için havuzu izlemek çok önemlidir.
csharp
public void GetPoolInfo()
{
using var connection = new SqlConnection(connectionString);
connection.Open();
Console.WriteLine($"Havuzdaki bağlantı sayısı: {SqlConnectionPool.GetPoolCount(connectionString)}");
Console.WriteLine($"Kullanımdaki bağlantı sayısı: {SqlConnectionPool.GetActiveCount(connectionString)}");
}
4. Gerçek Dünya Örneği: Pool Starvation ve Çözümü
Senaryo: Bir web uygulaması, yoğun saatlerde sık sık Timeout expired hatası alıyor. Log'larda "Timeout expired... obtaining a connection from the pool" mesajı görülüyor.
Adım Adım Analiz ve Çözüm:
-
Kod İncelemesi: Tüm
SqlConnectionkullanımlarınınusingbloğu içinde olduğundan emin olun. (En önemli adım). -
Max Pool Size Artırma:
connection string'eMax Pool Size=200ekleyerek havuzu geçici olarak büyütün. -
Uzun Sorguları Tespit Edin: Hangi sorguların çok uzun sürdüğünü bulun. SQL Server Profiler veya
sys.dm_exec_requestsile aktif sorguları izleyin. -
Sorgu Optimizasyonu: Uzun süren sorguları optimize edin (index ekleyin, sorguyu yeniden yazın).
-
Asenkron Kullanım: Özellikle web uygulamalarında, bağlantı açma işlemlerini
async/awaitile yapın. Thread havuzunu bloklamayın. -
Havuz Temizleme: Acil durumlarda,
SqlConnection.ClearPool()çağrısı yaparak havuzu sıfırlayabilirsiniz. Bu, yeni bağlantılar açılmasını sağlar.
5. Entity Framework Core ile Connection Pool Yönetimi
EF Core, varsayılan olarak connection pooling kullanır. AddDbContext ile kaydettiğiniz context, her istek (scope) için bir bağlantı açar ve kapatır.
EF Core'da Havuz Ayarları:
csharp
builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection"),
sqlOptions =>
{
sqlOptions.MaxPoolSize(100); // Max Pool Size ayarı
sqlOptions.ConnectionTimeout(15); // Connection timeout
});
});
EF Core'da Dikkat Edilmesi Gerekenler:
-
Dispose()Çağrılmalıdır: Context'leriusingiçinde veya DI ile (ScopedveyaTransient) kullanın. DI, context'i otomatik dispose eder. -
AsNoTracking()Kullanımı: Sadece okuma işlemlerindeAsNoTracking()kullanarak Change Tracker yükünü azaltın, bu da bağlantı süresini kısaltır. -
Uzun Süreli İşlemlerden Kaçının: Context üzerinde uzun süren işlemleri (ör. 10 dakikalık bir
SaveChanges) transaction içinde yapmayın. Mümkünse daha küçük batch'ler halinde işleyin.
6. Önleyici Stratejiler
-
Connection String'leri Yönetin: Farklı uygulamalar için farklı connection string'leri kullanın. Her uygulama kendi havuzunu yönetir.
-
Bağlantıları İzleyin: Uygulamanızda bir sağlık kontrolü (health check) endpoint'i oluşturarak havuz durumunu izleyin.
-
Yük Testi Yapın: Yük testi araçlarıyla (k6, JMeter) uygulamanızı yoğun istek altında test edin ve havuz davranışını gözlemleyin.
-
Loglama: Bağlantı açma/kapama işlemlerini loglayarak, anormal bir durumu (çok fazla açılma/kapanma) tespit edin.
-
Max Pool Size ve Connection Timeout'u Doğru Ayarlayın: Varsayılan ayarları projenizin ihtiyacına göre ayarlayın.
Sonuç:
Database Connection Pooling, .NET'in performansı artırmak için kullandığı hayati bir mekanizmadır. Ancak, yanlış kullanıldığında uygulamanızın en büyük zafiyetlerinden biri haline gelebilir. Pool Starvation (Havuz Tükenmesi) ve Connection Timeout sorunları, çoğunlukla bağlantıların kapatılmamasından (Leak) veya yetersiz havuz boyutundan kaynaklanır.
En İyi Pratikler:
-
Her zaman
usingbloğu kullanın. -
Havuz boyutunu ve timeout süresini iş yükünüze göre ayarlayın.
-
Uzun süren sorguları optimize edin.
-
Havuz durumunu izleyin ve loglayın.
-
Hata durumunda, havuzu temizleyin (ClearPool).
Unutmayın, sağlıklı bir bağlantı havuzu, sağlıklı bir uygulamanın temelidir.