GraphQL’i Laravel API’nizle entegre ettiniz. Her şey düzgün çalışıyor, sorgular temiz, ön uç geliştiriciler yalnızca ihtiyaç duydukları alanları talep ediyorlar. Ancak bir gün, 10 öğeden oluşan bir liste aniden 500 öğe içermeye başlıyor — ve API’niz, 80ms’de yanıt verirken 4 saniye yanıt vermeye başlıyor.
Çoğu durumda, sebep aynıdır: N+1 sorunu. İyi haber şu ki, nereye bakacağınızı bildiğinizde bunu birkaç dakikada düzeltebilirsiniz.
Sorunun Yeniden Oluşturulması
Sorunun Yeniden Oluşturulması
Basit bir durumu ele alalım: her biri kendi yazarı olan bir makaleler listesi.
query {
articles(first: 20) {
data {
titreauteur { nom }
}
}}
İlk bakışta ürkütücü bir şey yok. Ancak auteur ilişkisi her seferinde yeniden yüklenen bir PHP erişimcisi ya da yanlış yazılmış bir resolver ile basitçe çözülürse, işte veritabanı tarafında olanlar:
SELECT * FROM articles LIMIT 20; -- 1 sorgu
SELECT * FROM users WHERE id = 1; -- makale 1 için
SELECT * FROM users WHERE id = 2; -- makale 2 için
SELECT * FROM users WHERE id = 3; -- makale 3 için
-- ... her 20 makale için tekrarlanıyor
Toplamda 21 SQL sorgusu, tek bir GraphQL sorgusu için. Bunu 500 öğeden oluşan bir liste ile çarparsanız veya iki katmanlı ilişkilerle (makaleler → yorumlar → yorum yazarları) genişletirseniz, neden yanıt sürenizin böyle bir şekilde şiştiğini anlayacaksınız. Uygulama kodunuzda görünürde hiçbir değişiklik olmamasına rağmen.
Bu, N+1 sorunudur: başlangıçta yapılan bir sorgu (“1”) ve ardından listedeki her bir öğe için ek bir sorgu (“N”).
Neden Kolayca Oluşuyor?
Neden Kolayca Oluşuyor?
REST’te genellikle kontrolörde açık bir with('auteur') yazılır ve sorun kodda göz önüne gelir. GraphQL’de ise ilişki bir kez şemada tanımlanır ve her alınan sorgu için otomatik olarak çözülür — bu sınıfın tam olarak amacı. Ancak bu, N+1 riskinin yapısal olduğunu, kazara ortaya çıkmadığını gösterir: Bir alan bir liste üzerindeki bir ilişkiyi açığa çıkardığında, tehlike otomatik olarak mevcuttur.
Çözüm: Çözmeden Önce Gruplama
Çözüm: Çözmeden Önce Gruplama
N+1 sorununun her durumda nasıl çözüleceği tek bir cümlede özetlenebilir:
Asla bir döngünün içinde sorgu çalıştırmayın. Önce gerekli tüm anahtarları toplayın, sonra tek bir gruplama çağrısı ile çözün.
Lighthouse (Laravel ekosistemindeki en yaygın kullanılan GraphQL paketi), bu ilkeleri sizin için zaten uygulamaktadır:
type Article {
titre: String!
auteur: Utilisateur! @belongsTo
}
Arka planda @belongsTo her makale için yazar yüklemez. 20 user_id değerini toplayarak bir gruplama sorgusu yapar ve yanıtı oluşturmadan hemen önce gerçekleştirir:
SELECT * FROM articles LIMIT 20;
SELECT * FROM users WHERE id IN (1, 2, 3, ..., 20);
Liste büyüklüğünden bağımsız olarak toplamda 2 SQL sorgusu. Standart ilişki direktiflerini (@hasMany, @belongsTo, @belongsToMany) kullandığınızda Lighthouse’ın varsayılan davranışıdır — ek bir yapılandırma gerekmez.
Hesaplanan Alanlar
Hesaplanan Alanlar
Bir hesaplanan alanı açığa çıkardığınızda — örneğin ortalama bir puan, bir görüntüleme sayısı veya başka bir tablo üzerinden gereken bir toplam gibi — gerçek tuzak ortaya çıkar. Naif bir resolver aşağıdaki gibi görünecektir:
final class ResolveNoteMoyenne
{
public function __invoke(Article $article, array $args)
{
// ❌ Listeye gösterilen her makale için bir AVG() sorgusu
return Avis::where('article_id', $article->id)->avg();
}
}
N+1 sorunu burada devreye girmektedir, bu ilişkiler Eloquent standartları dışında taşınmıştır. Çözüm, kendi BatchLoader uygulamanızı oluşturmaktır:
final class NoteMoyenneLoader extends BatchLoader
{
public function resolve(): array
{
$ids = array_keys($this->keys);
// Beklemedeki tüm makaleler için tek bir grup çağrısı
$moyennes = Avis::whereIn(, $ids)
->selectRaw()
->groupBy()
->pluck(, );
$resultats = [];
foreach($this->keys as $id => ) {
$resultats[$id] = $moyennes[] ?? null;
}
return $resultats;
}
}
Sonuç, yerleşik direktiflerle elde edilenle aynıdır: Liste için tek bir GROUP BY sorgusu, kaç makale varsa olsun, 5 veya 5000 fark etmez.
Üretime Gitmeden Önce N+1 Nasıl Tespit Edilir?
Üretime Gitmeden Önce N+1 Nasıl Tespit Edilir?
Üç temel reflex yeterlidir:
- Laravel Debugbar geliştirme ortamında: her GraphQL sorgusu için yürütülen SQL sorgularının kesin sayısını gösterir. Eğer bu sayı döndürülen listenin boyutuyla artıyorsa, bir N+1 sorununuz var.
-
DB::listen()entegrasyon testlerinizde, yürütülen sorguların sayısı üzerinde bir doğrulama ile birlikte kullanın. - Laravel Telescope her sorgunun kökenini (stack trace) gösteren detaylı bir görünüm sunar.
Pratik kural: Bir GraphQL sorgusunun SQL sorgu sayısı, döndürdüğü listenin boyutuna asla bağlı olmamalıdır. Eğer bağlantı veya hesaplanan bir alanınız yeterince gruplama kullanmıyorsa, sorun oradadır.
Bu Sadece Bir Başlangıçtır
Bu Sadece Bir Başlangıçtır
N+1 genellikle GraphQL ile karşılaşılan ilk performans problemi olur — fakat bu sadece bir başlangıçtır. Ardından önbellekleme (ne zaman önbelleğe alınacak ve en önemlisi ne zaman geçersiz kılınacak), sorgu karmaşıklığının sınırlanması ve kötü yazılmış veya kötü niyetli bir istemciden etkilenmemek üzere oran sınırlama gibi konular gelir.
Tüm bunları — N+1, Redis önbellekleme, oran sınırlama, ölçeklenme ve Laravel ile bir GraphQL API’sinin tam inşasını ve bir Angular istemcisinden nasıl tüketileceğini — kapsamlı bir eğitimde bir araya topladım: [Laravel ile GraphQL].
Bu makale size zaman kazandırdıysa, eğitim çok daha fazlasını kazandırır.
N+1 veya Laravel ile GraphQL hakkında sorularınız mı var? Yorumlar açık.
Kaynak: Orijinal Makale


