Geliştirme ortamında on kayıtlık test verisiyle sorunsuz çalışan bir liste ekranı, üretimde binlerce kayıtla dakikalar sürebilir. Veritabanı CPU grafiği yükselir, connection pool tükenir, API timeout alır. Kök neden çoğu zaman N+1 sorgu problemidir: bir sorgu ana listeyi getirir, ardından her satır için ayrı sorgu tetiklenir. ORM'ler (Entity Framework Core, Hibernate, ActiveRecord) bu tuzağı kolaylaştırır çünkü lazy loading ve implicit navigation property erişimi arka planda SQL üretir.
N+1 nasıl oluşur?
Klasik örnek: 100 sipariş listelenirken her siparişin müşteri adı gösterilmek istenir.
var orders = await db.Orders.ToListAsync(); // 1 sorgu
foreach (var o in orders)
{
var name = o.Customer.Name; // Her iterasyonda 1 sorgu → N sorgu
}
Toplam 1 + N = 101 sorgu. N=1000 olduğunda binlerce round-trip oluşur. Ağ gecikmesi (1 ms) bile 101 ms minimum demektir; veritabanı yükü çok daha ağır.
Eager loading: Include ve ThenInclude
EF Core'da ilişkili veriyi tek sorguda (veya kontrollü sayıda sorguda) çekmek için Include kullanılır:
var orders = await db.Orders
.Include(o => o.Customer)
.Include(o => o.Lines)
.ThenInclude(l => l.Product)
.ToListAsync();
Bu genellikle JOIN içeren tek SQL üretir. Derin Include zincirleri cartesian explosion riski taşır: sipariş × satır × ürün satır sayısı patlar.
Split query
EF Core 5+ AsSplitQuery() ile birden fazla SELECT üretir ancak cartesian explosion'ı önler:
var orders = await db.Orders
.Include(o => o.Lines)
.AsSplitQuery()
.ToListAsync();
1 + birkaç sorgu = N+1'den çok daha iyi. Hangi stratejinin uygun olduğu veri şekline ve EXPLAIN ANALYZE çıktısına göre belirlenmelidir.
Projection: Select ile DTO
Tüm entity graph'ını yüklemek yerine yalnızca gerekli alanları projekte etmek hem sorgu sayısını hem veri transferini azaltır:
var dtos = await db.Orders
.Select(o => new OrderListDto
{
Id = o.Id,
CustomerName = o.Customer.Name,
Total = o.Lines.Sum(l => l.Quantity * l.UnitPrice)
})
.ToListAsync();
Tek SQL, yalnızca DTO kolonları. AutoMapper ProjectTo aynı prensibi korur. Liste ekranlarında entity döndürmek yerine projection varsayılan olmalıdır.
Lazy loading tuzağı
UseLazyLoadingProxies() açıkken navigation property'ye erişim otomatik sorgu tetikler. Kod review'da görünmez; yalnızca üretimde ortaya çıkar. Explicit loading (Entry().Collection().LoadAsync()) bilinçli kullanım sağlar. Çoğu sunucu tarafı API'de lazy loading kapalı tutulmalıdır.
Tespit yöntemleri
- EF Core log seviyesinde SQL çıktısı (Development)
- MiniProfiler veya Application Insights dependency tracking
- PostgreSQL pg_stat_statements — aynı pattern'in yüzlerce kez çalışması
- Integration test'te ILogger ile sorgu sayısı assert
// Test: en fazla 2 sorgu kabul et
var queries = new List();
db.Database.Log = sql => queries.Add(sql);
await handler.Handle(query);
Assert.True(queries.Count <= 2);
GraphQL ve N+1
GraphQL resolver'ları her alan için ayrı çalışabilir; müşteri alanı 100 sipariş için 100 sorgu üretir. DataLoader pattern batching ile çözer: aynı tick içindeki tüm customer ID'leri tek WHERE Id IN (...) sorgusunda yüklenir. Hot Chocolate, GraphQL.NET ve Apollo Server bu deseni destekler.
Dapper ve micro-ORM
Dapper N+1 üretmez; geliştirici manuel yazar. Ancak döngü içinde sorgu yazmak aynı hatadır. QueryAsync ile multi-mapping veya TVP/table-valued parameter ile batch okuma tercih edilmelidir.
Repository anti-pattern bağlantısı
Her repository metodu tek entity döndürüp üst katmanda döngü kurmak N+1'i davet eder. Use case odaklı sorgu metotları (GetOrdersWithCustomerAsync) veya specification pattern ile tek sorguda ihtiyaç duyulan graph tanımlanmalıdır.
Pagination ile birlikte
Sayfalama olmadan Include ile binlerce kayıt çekmek farklı ama eşit derecede tehlikeli bir performans sorunudur. Skip/Take veya keyset pagination (cursor) ile birlikte projection kullanın:
var page = await db.Orders
.OrderByDescending(o => o.Id)
.Where(o => o.Id < cursor)
.Take(20)
.Select(o => new OrderListDto { ... })
.ToListAsync();
Önleme checklist
- Lazy loading kapalı (sunucu API)
- Liste endpoint'lerinde projection zorunlu
- Code review'da döngü içi DB erişimi red flag
- CI'da kritik handler'lar için sorgu sayısı testi
- Staging'de gerçekçi veri hacmi ile load test
Özet
N+1 sorgu problemi ORM kullanan projelerin sessiz katilidir. Include, split query, projection ve lazy loading disiplini ile önlenir. GraphQL DataLoader ve Dapper döngü sorguları aynı prensiple yönetilir. Tespit için SQL loglama ve otomatik test; çözüm için use case bazlı tek sorgu tasarımı esas alınmalıdır.
Operasyonel perspektif
N+1 sorgu tuzagi konusunda üretim ortamında karşılaşılan senaryolar, geliştirme ortamından farklıdır. Trafik hacmi, eşzamanlı bağlantı sayısı, disk I/O ve replikasyon gecikmesi gibi faktörler tasarım kararlarını doğrudan etkiler. Metrik toplama ve düzenli kapasite gözden geçirmesi, sorunları kullanıcı şikâyetine dönüşmeden yakalamayı sağlar.
Kapasite planlama
Veritabanı katmanında CPU, bellek, disk throughput ve connection pool kullanımı birlikte izlenmelidir. Ani trafik artışlarında autoscaling uygulama katmanında mümkün olsa da veritabanı ölçeklendirmesi genellikle planlı yapılır. Read replica eklemek, connection pool boyutunu artırmak veya sorgu optimizasyonu yapmak gibi seçenekler yük testi sonuçlarına göre sıralanmalıdır.
Güvenlik ve erişim kontrolü
Veritabanı kullanıcıları en az ayrıcalık ilkesine göre tanımlanmalıdır. Uygulama hesabı yalnızca gerekli DML yetkilerine sahip olmalı; DDL ve yönetim işlemleri ayrı role ayrılmalıdır. Bağlantı string'leri secret manager'da tutulmalı, rotation politikası uygulanmalıdır. Audit log erişimi yalnızca denetim ve operasyon rollerine açık olmalıdır.
Ekip disiplini ve dokümantasyon
Mimari kararlar Architecture Decision Record (ADR) ile belgelenmelidir. Onboarding dokümanında bu konuya özel bölüm yer almalı; yeni geliştiriciler global filter, routing kuralı veya yedekleme penceresi gibi kritik detayları atlamamalıdır. Code review checklist'ine ilgili maddeler eklenmesi regresyon riskini azaltır.
Kalite kapıları
- Pull request'te ilgili entegrasyon testlerinin çalışması
- Staging ortamında gerçekçi veri hacmi ile smoke test
- Performans regression eşiği (p95 latency, sorgu sayısı)
- Migration rollback planının PR açıklamasında belirtilmesi
Bu disiplinler tek başına mucize yaratmaz; ancak veritabanı katmanındaki hataların maliyeti yüksek olduğundan, erken yakalama yatırımı uzun vadede ödenir. Post-incident review'larda kök neden analizi veritabanı tasarımına geri beslenmelidir.
Operasyonel perspektif
N+1 sorgu tuzagi konusunda üretim ortamında karşılaşılan senaryolar, geliştirme ortamından farklıdır. Trafik hacmi, eşzamanlı bağlantı sayısı, disk I/O ve replikasyon gecikmesi gibi faktörler tasarım kararlarını doğrudan etkiler. Metrik toplama ve düzenli kapasite gözden geçirmesi, sorunları kullanıcı şikâyetine dönüşmeden yakalamayı sağlar.
Kapasite planlama
Veritabanı katmanında CPU, bellek, disk throughput ve connection pool kullanımı birlikte izlenmelidir. Ani trafik artışlarında autoscaling uygulama katmanında mümkün olsa da veritabanı ölçeklendirmesi genellikle planlı yapılır. Read replica eklemek, connection pool boyutunu artırmak veya sorgu optimizasyonu yapmak gibi seçenekler yük testi sonuçlarına göre sıralanmalıdır.
Güvenlik ve erişim kontrolü
Veritabanı kullanıcıları en az ayrıcalık ilkesine göre tanımlanmalıdır. Uygulama hesabı yalnızca gerekli DML yetkilerine sahip olmalı; DDL ve yönetim işlemleri ayrı role ayrılmalıdır. Bağlantı string'leri secret manager'da tutulmalı, rotation politikası uygulanmalıdır. Audit log erişimi yalnızca denetim ve operasyon rollerine açık olmalıdır.
Ekip disiplini ve dokümantasyon
Mimari kararlar Architecture Decision Record (ADR) ile belgelenmelidir. Onboarding dokümanında bu konuya özel bölüm yer almalı; yeni geliştiriciler global filter, routing kuralı veya yedekleme penceresi gibi kritik detayları atlamamalıdır. Code review checklist'ine ilgili maddeler eklenmesi regresyon riskini azaltır.
Kalite kapıları
- Pull request'te ilgili entegrasyon testlerinin çalışması
- Staging ortamında gerçekçi veri hacmi ile smoke test
- Performans regression eşiği (p95 latency, sorgu sayısı)
- Migration rollback planının PR açıklamasında belirtilmesi
Bu disiplinler tek başına mucize yaratmaz; ancak veritabanı katmanındaki hataların maliyeti yüksek olduğundan, erken yakalama yatırımı uzun vadede ödenir. Post-incident review'larda kök neden analizi veritabanı tasarımına geri beslenmelidir.