Foruma hoş geldin 👋, Ziyaretçi

Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

N+1 problemini çözerken memory kullanımını da kontrol altında tutmak için `cursor()` ve lazy Eager Loading kombinasyonum

websterx

Üye
Katılım
14 Mart 2026
Mesajlar
14
Merhaba arkadaşlar, bugün başımı çok ağrıtan bir sorundan ve bulduğum temiz çözümden bahsedeceğim. Laravel'de büyük veri setleriyle uğraşırken, N+1 sorgu problemini çözmek için `with()` kullanmak ilk refleksimiz. Ama işler biraz büyüyünce, bu sefer de memory limit hatasıyla karşılaşabiliyoruz. Tüm kullanıcıları ve onların yüzlerce siparişini tek seferde çekmeye kalktığınızda, sunucunuz "Allah korusun" diyebilir. İşte benim kullandığım, iki dünyayı bir araya getiren kombinasyon.

🔥 Karşılaştığım Sorun

Bir raporlama sisteminde, aktif tüm kullanıcıların (diyelim 50.000) ve bu kullanıcılara ait son bir yıllık siparişlerin toplam tutarını hesaplamam gerekiyordu. Klasik `User::with('orders')->get()` yaklaşımı, önce tüm kullanıcıları, sonra da ilişkili tüm siparişleri (milyonlarca satır) aynı anda memory'e yükledi. Sonuç? `Allowed memory size of X bytes exhausted`. `chunk()` metodunu denedim ama bu sefer de her chunk için ayrı ayrı ilişkileri yükleme (N+1) veya tüm ilişkileri tekrar tekrar yükleme sorunu ortaya çıkıyordu.

💡 Çözüm Fikri: Cursor ve Lazy Eager Loading

Aklıma iki güzel özelliği birleştirmek geldi: `cursor()` ve `loadMissing()`. `cursor()`, bir Generator döndürür ve tüm sonucu bir kerede değil, tek bir satır (model instance'ı) halinde memory'e yükler. `loadMissing()` ise, Lazy Eager Loading yaparak, sadece ihtiyaç duyulan modeller için ilişkileri yükler. İkisini birleştirince, hem memory dostu hem de N+1'den arınmış bir akış elde ettim.

👨‍💻 Uygulama ve Kod Örnekleri

İşte benim uyguladığım temiz çözümün kodu:

PHP:
// Raporu oluşturacak metodumuz
public function generateUserOrderReport()
{
    $reportData = [];

    // 1. Tüm kullanıcıları cursor ile tek tek al
    foreach (User::where('active', true)->cursor() as $user) {
        // 2. Bu kullanıcı için siparişleri LAZY olarak yükle (N+1'i önler)
        $user->loadMissing(['orders' => function ($query) {
            $query->where('created_at', '>=', now()->subYear());
        }]);

        // 3. Sipariş toplamını hesapla
        $totalAmount = $user->orders->sum('amount');

        // 4. Rapor dizisine ekle (gerekiyorsa)
        $reportData[] = [
            'user_id' => $user->id,
            'name' => $user->name,
            'total_order_amount' => $totalAmount
        ];

        // 5. ÖNEMLİ: İlişkili koleksiyonu temizle (memory'den at)
        unset($user->orders);
    }

    return $reportData;
}

Burada kritik noktalar:
- `cursor()` sayesinde tek seferde sadece bir User modeli memory'de.
- `loadMissing()` ile o anki kullanıcının sadece gerekli siparişleri (son bir yıl) yükleniyor. Bu, `with()` ile tüm siparişleri çekmekten çok daha verimli.
- `unset($user->orders);` satırı, işi biten ilişkili koleksiyonu memory'den manuel olarak boşaltıyor. Bu küçük dokunuş, uzun döngülerde memory sızıntısını engellemek için altın değerinde.

📈 Performans ve Sonuç

Bu yöntemi uygulamadan önce, 50k kullanıcı için script maksimum memory limitine (512MB) çarpıp ölüyordu. Bu kombinasyonu uyguladıktan sonra, memory kullanımı sabit bir seviyede (yaklaşık 10-15MB civarında) kaldı ve işlem sorunsuz tamamlandı. Sorgu sayısı da optimize oldu çünkü her kullanıcı için ayrı sipariş sorgusu atılmadı (`loadMissing` bunu akıllıca grupluyor).

Peki siz Laravel'de büyük veri işlerken benzer sorunlarla karşılaştınız mı? `chunk()` ve `cursor` arasında tercihiniz ne? Ya da farklı bir memory optimizasyon taktiğiniz var mı? Yorumlarda deneyimlerinizi paylaşın, tartışalım!
 

Tema özelleştirme sistemi

Bu menüden forum temasının bazı alanlarını kendinize özel olarak düzenleye bilirsiniz.

Zevkine göre renk kombinasyonunu belirle

Tam ekran yada dar ekran

Temanızın gövde büyüklüğünü sevkiniz, ihtiyacınıza göre dar yada geniş olarak kulana bilirsiniz.

Geri