Liste sayfalarında performans sorunu çoğu zaman “yavaş sunucu” değildir; aynı isteğin içinde onlarca gizli sorgu çalışmasıdır. Doctrine’da N+1, ilişkili varlıklar döngü içinde tembel yüklendiğinde ortaya çıkar. Bu not, CPalius benzeri içerik listelerinde sorunu erken yakalamak için kullandığımız pratikleri özetler. Kısa tutulmuş gibi görünse de buradaki alışkanlıklar, aylar sonra ortaya çıkan gizemli gecikmeleri engeller.
İlk kural: geliştirme ortamında sorgu sayısını görünür kılın. Bir middleware veya toolbar ile SELECT sayısını ve etkilenen tabloları loglamak, regresyonu PR aşamasında yakalar. “Bu sayfa 8 SELECT’i geçmesin” gibi basit bir bütçe, ekibi bilinçli join kullanmaya iter. Üretimde aynı bütçeyi sert hata yapmak yerine uyarı olarak tutmak daha güvenlidir. Görünmeyen maliyet, yönetilemeyen maliyettir.
İkinci kural: liste sorgusunda gerçekten ihtiyaç duyulan ilişkileri baştan yükleyin. Yazar adı, birincil kategori ve kapak alanları kartta görünüyorsa, bu ilişkiler döngüden önce join/select edilmelidir. Ancak her şeyi join etmek de yanlıştır; gereksiz cartesian çarpım paginator’ı bozar. Many-to-many ilişkilerde Paginator kullanırken fetchJoinCollection davranışını bilinçli seçin. “Hepsini join ettim, artık hızlı” çoğu zaman yanlış bir güvendir.
Üçüncü kural: sayım ve listeyi ayırın. COUNT sorgusu ile satır çeken sorgu aynı DQL’i körü körüne paylaşmak zorunda değildir. Sıralama ifadeleri, gereksiz join’ler ve select alanları sayım maliyetini şişirir. Ayrı bir sayım sorgusu bazen daha net ve daha hızlıdır. Özellikle filtre sayısı arttıkça bu ayrımın değeri yükselir.
Dördüncü kural: JSON veri alanlarına güvenip ilişki gibi davranmayın. Node::data içindeki anahtarlar esneklik sağlar ama indeks ve join ihtiyacını ortadan kaldırmaz. Sık filtrelenen alanlar örneğin post_sub_type alan indeksine yazılmalıdır. Aksi halde her liste “tüm satırları çek, PHP’de ele” anti-pattern’ine kayar. Esneklik ile sorgulanabilirlik arasında bilinçli bir denge kurun.
Beşinci kural: Twig tarafında gizli N+1’i unutmayın. Şablon içinde post.author.profile veya category.children gezmek, controller’da sorunsuz görünen bir sorguyu patlatabilir. Kısmi şablonlara veri bilerek enjekte edin; şablonun entity grafiğinde serbest dolaşmasına izin vermeyin. Sunum katmanı, veri erişim stratejisini sessizce sabote edebilir.
Altıncı kural: ölçmeden optimize etmeyin. EXPLAIN planı, yavaş sorgu logu ve gerçek sayfa zamanlaması olmadan “şu join’i ekleyelim” demek yanlış indekslere yol açabilir. Özellikle locale + status + published_at kombinasyonları için bileşik indeksler, blog arşivlerinde sık kazanç sağlar. Tahmine dayalı mikro optimizasyonlar bakım maliyetini artırır.
Yedinci kural: testlere bir “sorgu bütçesi” iddiası ekleyin. Fonksiyonel testte sayfa 200 dönse bile SELECT sayısı eşik aşıyorsa test kırılmalıdır. Bu, refactor sırasında N+1’in geri gelmesini engeller. Küçük bir assertion, büyük bir operasyonel tasarruftur.
Sekizinci kural: önbelleği N+1’in üstüne bandaj gibi yapıştırmayın. Yanlış sorguları önbelleklemek, yanlış sonucu daha hızlı sunar. Önce sorguyu düzeltin, sonra gerçekten pahalı ve stabil sonuçları önbellekleyin. Önbellek anahtarları locale, filtre ve kullanıcı yetkisine duyarlı olmalıdır.
Son olarak ekip alışkanlığına dönüştürün. Yeni bir liste endpoint’i açıldığında kontrol listesine sorgu sayısı ekran görüntüsü ekleyin. Küçük bir disiplin, ilerideki performans yangınlarını azaltır. Code review’da “bu döngüde ilişki geziliyor mu?” sorusunu standart hale getirin; çoğu N+1 tam olarak orada doğar. Release notlarına da kısa bir performans bölümü eklemek, gerilemeyi görünür kılar. Şüpheli bir sayfada önce toolbar’a bakın, sonra tahmine yönelin. Aşağıdaki örnek, yazar ilişkisini baştan yükleyen minimal bir QueryBuilder desenidir; kendi listenize göre alanları daraltın, sayfa bütçesini ölçerek doğrulayın ve eşik aşıldığında alarmı görünür bırakın. Performans, bir kerelik kahramanlık değil; tekrarlanan mühendislik hijyenidir.