Staging ortamında sorunsuz çalışan bir API, gerçek trafik geldiği anda çökebilir. Başarısızlık nadiren dramatik olur; bu, binlerce küçük darbeyle gelen bir ölümdür. Burada engellenmiş bir thread, orada gereksiz bir sorgu... Hafif yük altında bu küçük verimsizlikler gizlenir. Üretim (production) baskısı altında ise bunlar; thread pool açlığı, veritabanı tıkanıklığı ve kullanıcı güvenini yok eden yanıt süreleri olarak birikerek büyür. Performans iyileştirme, tek bir sihirli çözüm bulmak değildir. Her katmanın thread, bellek ve veritabanı bağlantılarının kıtlığına saygı duyduğu bir sistem inşa etmekle ilgilidir. Bu kaynakları sınırlı olarak ele aldığınızda, kesintilere tepki vermeyi bırakır ve onları önlemeye başlarsınız.
Sync-over-Async Sizi Öldürmeden Siz Onu Öldürün
Yüksek trafikli ASP.NET Core uygulamalarındaki en yıkıcı desen, sync-over-async'tir. Birisi, değere hemen ihtiyaç duyduğu ve çağrı yığınını (call stack) yeniden yapılandırmak istemediği için asenkron bir metod üzerinde .Result veya .Wait() çağırdığında bunu görürsünüz. Bu karar, çağıran thread'i engeller. Thread, başka bir yerde zaten gerçekleşmekte olan bir işi bekleyerek boşta kalır ancak çalışma zamanı (runtime) onu başka bir istek için yeniden kullanamaz.
Yeterince istek bunu yaptığında, thread pool açlık çeker. İşlemciler meşgul olmadığı için CPU grafiğiniz sağlıklı görünür, ancak gecikme süreniz (latency) patlar. İstekler, asla serbest kalmayacak olan thread'leri bekleyerek kuyruğa girer. Çözüm mekaniktir ancak disiplin gerektirir: Çağrı yığını boyunca her yerde await kullanın. Eğer bir metod asenkron bir API çağırıyorsa, kendisi de asenkron olmalıdır. Burada kestirme yol yoktur. Kaldırdığınız her senkron bloklama, gerçek trafik için size alan kazandırır.
Bağlantısı Kesilen İstemciler İçin İş Gücünü Boşa Harcamayı Bırakın
İstemciler bağlantıyı keser. Tarayıcılar sekmeleri kapatır. Mobil uygulamalar sinyal kaybeder. Eğer sunucunuz istemcinin gittiğini bilmezse; veritabanı sorguları çalıştırmaya, JSON ayrıştırmaya (parsing) ve kimsenin almayacağı bir yanıt için thread'leri tüketmeye devam eder. Bunu destekleyen her asenkron işleme bir CancellationToken geçirin. Bu; Entity Framework sorguları, HttpClient ile yapılan HTTP çağrıları ve tüm uzun süreli arka plan işleri anlamına gelir.
Bağlantı koptuğunda, token iptali tetikler ve iş anında durur. Bu, veritabanı CPU döngülerini korur ve thread'leri havuza daha hızlı geri döndürür. Metot imzalarındaki bu küçük değişiklik, yük altında kendini amorti eder.
Değişmeyen Şeyleri Önbelleğe Alın
Ürün kataloğunuz muhtemelen her istek arasında değişmez. Yapılandırma bayraklarınız (configuration flags) ise kesinlikle değişmez. Yine de birçok API, aynı statik veri için veritabanına tekrar tekrar gider. ASP.NET Core'daki output caching, işlenmiş yanıtları saklamanıza ve denetleyicilerinize (controllers) veya veritabanınıza tekrar dokunmadan bunları doğrudan bellekten sunmanıza olanak tanır.
Etiket tabanlı geçersiz kılmayı (tag-based invalidation) dikkatli kullanın. Bir ürünü güncellediğinizde, yalnızca o kategori veya öğeyle ilişkili etiketi geçersiz kılın. Tüm önbelleği temizlemenize gerek yoktur. Bu, önbellek isabet oranınızı (cache hit ratio) yüksek, veritabanı sorgu sayınızı ise düşük tutar.
Önce Veri Erişimini Düzeltin
Çoğu API'de istek süresinin büyük bir kısmını veritabanı çağrıları tüketir. Başka herhangi bir şeyi optimize etmeden önce buraya bakın.
Eğer veriyi sadece görüntülemek için sorguluyorsanız ve asla güncelleme yapmayı planlamıyorsanız, Entity Framework sorgularınıza AsNoTracking() ekleyin. EF Core, değişiklik takibini (change tracking) ve anlık görüntü (snapshot) oluşturmayı atlar.
