Neler yeni

Seni de aramızda görmek istiyoruz! 🚀

Bilgi paylaştıkça çoğalır. Şu an misafir olarak buradasın ama sadece birkaç saniyeni ayırarak ücretsiz üye olabilir; binlerce içeriğe erişebilir, sorularını sorabilir ve topluluğumuzun bir parçası olabilirsin. Geliştirmek, öğrenmek ve paylaşmak için hemen aramıza katıl!

GraphQL N+1 Problemi — DataLoader ile Performans Sorununu Çözme

baki

Sahip
Yönetici

🔗 GRAPHQL N+1 PROBLEMİ — API'NİZ NEDEN YAVAŞ? 🔗
Esnek sorgular güzel ama beraberinde gizli bir performans tuzağı getiriyor
​




Merhaba! 🔗

GraphQL'in esnekliği harika ama dikkatli tasarlanmazsa ciddi bir performans problemi yaratabiliyor: N+1 query problemi. Bu konuda bu klasik sorunu ve DataLoader ile çözümünü derinlemesine ele aldım.

🤔 N+1 Problemi Nedir?

Bir GraphQL sorgusu, ilişkili veri getirirken her bir öğe için AYRI bir veritabanı sorgusu tetiklerse bu N+1 problemi. "1" ana sorgu + "N" ilişkili veri sorgusu.

JavaScript:
// ❌ KÖTÜ — N+1 problemi yaratan resolver
 
const cozumleyiciler = {
    Query: {
        yazarlar: async () => {
            return await Yazar.findAll();  // 1 sorgu — tüm yazarları getir
        }
    },
    Yazar: {
        kitaplar: async (yazar) => {
            // ⚠️ HER YAZAR İÇİN AYRI SORGU!
            // 100 yazar varsa bu 100 kez çalışır = 100 ayrı sorgu
            return await Kitap.findAll({ where: { yazarId: yazar.id } });
        }
    }
};
 
// Şu sorgu için:
// query {
//   yazarlar {
//     isim
//     kitaplar { baslik }  ← Her yazar için ayrı sorgu tetikliyor!
//   }
// }
// 100 yazar = 1 (yazarlar) + 100 (her yazarın kitapları) = 101 SORGU!
 
 
// ✅ İYİ — DataLoader ile batch (toplu) sorgulama
const DataLoader = require('dataloader');
 
function kitapYukleyiciOlustur() {
    return new DataLoader(async (yazarIdleri) => {
        // Tüm yazar ID'leri TEK BİR sorguda toplanıyor
        console.log(`📦 Batch sorgu: ${yazarIdleri.length} yazar için TEK sorgu`);
 
        const tumKitaplar = await Kitap.findAll({
            where: { yazarId: yazarIdleri }  // IN sorgusu — tek seferde
        });
 
        // Sonuçları, orijinal ID sırasına göre gruplandır
        // DataLoader'ın beklediği format: her ID için bir dizi sonuç
        return yazarIdleri.map(yazarId =>
            tumKitaplar.filter(kitap => kitap.yazarId === yazarId)
        );
    });
}
 
const cozumleyicilerDogru = {
    Query: {
        yazarlar: async (_, __, context) => {
            return await Yazar.findAll();
        }
    },
    Yazar: {
        kitaplar: async (yazar, _, context) => {
            // DataLoader otomatik olarak bu çağrıları TOPLAR
            // ve tek bir batch sorguda birleştiriyor!
            return await context.kitapYukleyici.load(yazar.id);
        }
    }
};
 
// Her request için YENİ bir DataLoader instance'ı oluşturulmalı
// (context oluşturulurken):
function contextOlustur() {
    return {
        kitapYukleyici: kitapYukleyiciOlustur()
    };
}
 
// Sonuç: 100 yazar = 1 (yazarlar) + 1 (TÜM kitaplar batch) = SADECE 2 SORGU!

🎯 DataLoader'ın Sihri Nasıl Çalışıyor?

DataLoader, aynı "event loop tick"i içinde yapılan tüm `.load()` çağrılarını topluyor (batching), sonra bunları TEK bir toplu sorguda birleştiriyor. Ayrıca varsayılan olarak sonuçları cache'liyor — aynı ID için tekrar sorgu yapmıyor.

🔍 N+1 Problemini Tespit Etme

SQL:
-- Veritabanı sorgu loglarınızı izleyin
-- Eğer tek bir GraphQL isteği için onlarca/yüzlerce benzer
-- sorgu görüyorsanız (sadece WHERE koşulundaki ID değişiyorsa),
-- bu klasik bir N+1 belirtisi
 
-- Örnek şüpheli log paterni:
-- SELECT * FROM kitaplar WHERE yazar_id = 1;
-- SELECT * FROM kitaplar WHERE yazar_id = 2;
-- SELECT * FROM kitaplar WHERE yazar_id = 3;
-- ... (100 kez tekrar) ...

🛠️ Diğer Çözüm Yaklaşımları

🔹 JOIN kullanarak tek sorguda getirme: Bazı basit senaryolarda, GraphQL katmanına gelmeden önce veritabanı seviyesinde JOIN ile ilişkili veriyi birlikte çekmek mümkün

🔹 Query complexity limiting: Çok derin/karmaşık iç içe sorgulara karşı limit koyarak kötüye kullanımı önleme

🔹 Persisted queries: Production'da sadece önceden onaylanmış sorgu şablonlarına izin vererek hem güvenlik hem performans öngörülebilirliği sağlama

💡 Genel Kural

GraphQL resolver'larında "ilişkili veri getiren" her fonksiyon, N+1 riski taşıyor olarak değerlendirilmeli. DataLoader (veya benzeri batching mekanizması) kullanmak, GraphQL API'lerinde neredeyse her zaman gerekli bir best practice.




💬 N+1 PROBLEMİ İLE KARŞILAŞTINIZ MI? 💬​

GraphQL API'nizde performans sorunu yaşadınız mı? Nasıl çözdünüz? 🔗👇
 

Konuyu görüntüleyenler

Üst