Performans çalışmaları, “Gecikmeyi %80 azalttık” hikayesi gibi göz alıcı bir üne sahiptir. Ancak çoğu gün bu böyle değildir. Genellikle biraz temizlik yapmanız gerekir: Farkında olmadan yarattığınız sorguları bulup sessizce silersiniz. Bugün de böyle geçti. Bir uygulama üzerinde sürdürülen bir temizlik: kullanmadığınız şeyler için veritabanından sorgu istemeyin.
Bu desenleri gözden geçireceğim, çünkü belirli bir yaştaki herhangi bir Laravel uygulamasına genelleştirilebilir.
İlk olarak, görünmeyeni görünür kılın
İlk olarak, görünmeyeni görünür kılın
Görmediğiniz N+1’leri düzeltemezsiniz. İlk adım, yalnızca yerel/geliştirme ortamında çalışan bir N+1 dedektörünü devreye almaktı — beyondcode/laravel-query-detector. İsteğin yaşam döngüsüne bağlanır, Eloquent ilişki yüklemelerinizi izler ve klasik döngü ve tembel yükleme desenini tespit ettiğinde bağırır (konsolda ya da isterseniz katı bir şekilde bir istisna olarak).
“Geliştiriciye özel” kısmı önemlidir. Üretim ortamında hiçbir zaman bir sorgu dedektörü çalıştırmak istemezsiniz — bu ek yük getirir ve sadece bir geliştirici yardımıdır, çalışma zamanı koruyucusu değildir. Dolayısıyla, üretim ortamında değilken kaydedildiği bir ortam kontrolü arkasına yerleştirilir:
public function register(): void
{
if ($this->app->environment('local', 'testing')) {
$this->app->register(\BeyondCode\QueryDetector\QueryDetectorServiceProvider::class);
}
}Bunu yemek pişerken sadece aradığınız bir duman dedektörü gibi düşünün. Tasarım gereği gürültülü — önemli olan da bu. Gürültü, yapılacaklar listenizdir.
Kullanmadığınız eager load’lar, N+1’lerin bir maskesi
Kullanmadığınız eager load’lar, N+1’lerin bir maskesi
Burada tersine bir durum var. N+1’leri with() ekleyerek düzeltme konusunda eğitimliyiz. Ancak tam tersini yapan hata da sık görülür ve hemen hemen hiçbir zaman yakalanmaz: bir ilişkiyi eager-load edersiniz ve sonra… görünümde asla kullanmazsınız.
Liste ekranları en kötü suçlulardır. Biri bir liste oluşturur, creator ve approver‘ı eager-load eder ki tablo isimleri gösterebilirsin, ardından bir yeniden tasarım bu sütunları düşürür — ancak with(['creator', 'approver']) kalır. Artık her sayfa yüklemesi, hiçbir yere taşımadığınız ilişkileri besler. N+1 değil (döngü yok), ama aynı hastalık: Para ödediğiniz ve attığınız sorgular.
Çözüm sıkıcı ama tatmin edici — Blade’i okuyun, bileşeni okuyun ve tüketici olmayan eager load’ları silin:
// Önce: görünümde artık geçmeyen ilişkileri besleyerek
$services = Service::query()
->with(['creator', 'approver'])
->latest()
->paginate();
// Sonra: tablo yalnızca neyi gösteriyorsa onu yükleyin
$services = Service::query()
->latest()
->paginate();
Birkaç bileşen üzerinde — hizmetler, katalog, abonelikler, tüketiciler, admin kullanıcı listesi — bu tek disiplin, şaşırtıcı bir şekilde birçok gidiş gelişin önüne geçti. Ders: bir eager load, “Bunu kullanacağım” iddiasıdır. Bu iddiaları periyodik olarak denetleyin, çünkü görünümler, bunların arkasındaki sorgulardan daha hızlı değişir.
Her istek başına bir kez hesaplama: memoizasyon
Her istek başına bir kez hesaplama: memoizasyon
Bugün yapılan iki kazanım aynı yapıya sahipti: Bir isteğin süresi boyunca sabit bir değerin her seferinde talep edilmesiydi.
İlki, bir varsayılan bağlantı çözücüsüydü — hangi veritabanı bağlantısını kullanacağını belirleyen bir yöntem, her çağrıda biraz çalışıyordu ve tek bir istekte her yerde çağrılıyordu. İkincisi, her bileşen renderında yeniden hesaplanan bir okunmamış bildirim sayısını gösteren bir yan kenardı.
Hiçbirinin isteğin ortasında değişmesine gerek yok. Bu nedenle memoize edelim:
protected ?string $defaultConnection = null;
public function getDefaultConnectionName(): string
{
return $this->defaultConnection ??= $this->resolveDefaultConnection();
}??= null-coalescing atama işlemi, tüm numaradır: ilk çağrıda hesapla, geri kalan istek için cached değeri yeniden kullan. Bir singleton veya kapsamlı bir çözücü için, bu kadar yeter. Birden fazla istekte sabit olan değerler için (sadece birinin içinde değil), bunun yerine cache kullanın — bu büyük konulara götürür.
Birkaç sayıyı göstermek için 20 sorgu ateşleyen panel
Birkaç sayıyı göstermek için 20 sorgu ateşleyen panel
Pano uygulamaları sorgu tuzaklarıdır. Bir dizi istatistik kartınız var — toplam bu, aktif o, bekleyen diğer — ve tembel uygulama bir Model::count() başına bir sorgudur. Altı kart ve birkaç tablo arasında aniden ana sayfanız uygulamanın en ağır okuması haline gelir, her login’de çalışır.
Burada iki değişim üst üste bindirilmiş:
Tablo başına sayıları birleştirin. Aynı tabloya karşı N ayrı
count()sorgusu yerine, ihtiyacınız olan tüm toplamları tek bir gruplu sorguda alın. Her tablo için veritabanına bir sefer gidin, her metrik için bir sefer değil.Sonucu cache’leyin. Pano istatistiklerinin gerçek zamanlı olması gerekmez. Bunları kısa ömürlü bir cache içinde sarmak, pencerenin ilk ziyaretçisinin sorguyu ödemesini ve sonraki herkesin önbelleği kullanmasını sağlar:
public function stats(): array
{
return Cache::remember('dashboard.stats', now()->addMinutes(5), function () {
return [
'services' => $this->serviceCounts(), // tek bir gruplu sorgu
'consumers' => $this->consumerCounts(), // tek bir gruplu sorgu
// ...
];
});
}Buradaki takas samimiyettir: bir cache’li panel biraz eski olabilir. Bir operasyon özetine bu çoğunlukla doğru çağrıdır — hiç kimse aktif sayının 1,204 mü yoksa 1,205 mi olduğuna karar vermiyor. Eğer bir sayı gerçekten canlı olmalıysa, o sayıyı cache’den çıkarın. Kapsayıcı bir şekilde cache yapma refleksini bırakın; kabul edilebilir eski olan yerlerde cache yapın, bu çoğu panoda mevcuttur.
Veritabanı bağlantısı hakkında kısa bir söz
Veritabanı bağlantısı hakkında kısa bir söz
Günün diğer yarısı sorgular değil, bağlantı hakkında oldu. Derinlemesine gitmeden öne çıkarmaya değer iki fikir: isteğe bağlı olarak kalıcı veritabanı bağlantılarını etkinleştirmek (her istekte uzak bir veritabanı için TCP/handshake kurulumu için ödeme yapmaktan kaçınmak) ve nerede zaman harcadığınızı ölçmek için küçük bir bağlantı gecikmesi tanısı eklemek. Kalıcı bağlantılar, “önce ölç” özelliğidir — bağlantı kurulumu darboğazınızsa yardımcı olurlar, ancak değilse zarar verebilirler, bu nedenle tanı aracı önce gelmelidir. Ölçmeden optimizasyon, sadece batıl inançtır.
Bunu bir testle güvence altına alın
Bunu bir testle güvence altına alın
Böyle bir temizlik geçişinin riski regresyon: altı ay sonra biri bir eager load’u geri ekler ve kimse fark etmez. Bu nedenle, temizliğin bir koruma sağlaması gerekir. Sorgu sayısını Pest’te doğrulayarak davranışı sabitleyebilirsiniz:
it('renders the services index without hydrating unused relations', function () {
Service::factory()->count(5)->create();
DB::enableQueryLog();
Livewire::test(ServiceIndex::class)->assertOk();
// Liste, belli sınırlı sayıda sorguyla görünmelidir —
// ne bir satır başına, ne de kullanılmayan ilişkileri yanında sürüklememelidir.
expect(count(DB::getQueryLog()))->toBeLessThanOrEqual(5);
DB::disableQueryLog();
});Bu, kaba bir araçtır — bir sorgu sayısı üst sınırı — ama burada kaba olanın önemi vardır. Test, kesin sorgu planını belgelendirmek değildir; asıl önemli olan, bir gün sessizce bir N+1’i geri getirerek sayıyı tavan yaptıran günü yakalamaktır. O regresyon gerçekten önemlidir.
Sonuç
Sonuç
Bugün yapılan değişikliklerin hiçbiri akıllıca değildi. Önemli olan da bu. Bu katmanda performans sertleştirme genellikle kaldırma ile ilgilidir — kullanılmayan eager load’lar, yeniden hesaplanan değerler, her metriğe yönelik sayım sorguları — ayrıca optimizasyon yapmadan önce ölçme disiplinini ve temizliğin temiz kalması için bir veya iki test yapmayı gerektirir. Geliştirici ortamında bir dedektör kurun, her eager load’u haklı çıkarmanız gereken bir iddia olarak düşünün, isteğe bağlı sabit değerleri memoize edin ve tahammül edilebilecek şeyleri cache’leyin. Bunu yılda bir ya da iki kez yapın ve uygulama sessizce hızlı kalsın, kimse kahraman bir optimizasyon PR’ı yazmadan.
Kaynak: Orijinal Makale


