Laravel'de büyük tablo sorgularında chunk metodu ile hafızayı verimli kullanma yöntemi



3 dakikalık okuma
December 19, 2018

Farz edin veritabanınında bulunan bir tabloda 10,000 veya daha fazla sayıda kayıt var. Ve siz bu tabloda 1 kaydı değiştirmek istiyorsunuz; fakat bu işi de sql sorgusu ile de yapamadığınızı farz edelim.(Kurgusal bir örnek olduğundan böyle farz edin :) )

Bu işi foreach ile aşağıdaki şekilde yapardık değil mi ?

$users = User::all();
foreach ($users as $user) {
  $some_value = ($user->some_field > 0) ? 1 : 0;
  // might be more logic here
  $user->update(['some_other_field' => $some_value]);
}

Yukarıdaki problemi görebiliyor musunuz? 10,000 veriyi çekip $user değişkenine aktardık. Yani hafızaya aldık. Bu iş veritabanında 50 - 100 kullanıcı için ve 10,000 kayıt için belki tolere edilebilir gibi duruyor; ama ilerisi açısından hem kullanıcı çoğaldığında hem de kayıt sayısı çoğaldığında sunucu hafızasını ne kadar meşgul edeceğimizi varın siz düşünün. Hatta out of memory hata mesajı ile internal 500 hatası bile verdirebilirsiniz sunucuya.

İşte bu tür durumlar için Laravel’de harika bir metodumuz var. “chunk()” metodu. Yukarıdaki mantığı chunk metodu ile kullanarak şöyle kodlarsak:

User::chunk(100, function ($users) {
  foreach ($users as $user) {
    $some_value = ($user->some_field > 0) ? 1 : 0;
    // might be more logic here
    $user->update(['some_other_field' => $some_value]);
  }
});

Ne yapmış olacağız? Veritabanından verileri 100’er 100’er alarak kontrol ediyor ve değiştiriyor olacağız. Daha ham tabirle: “Her 100 alışından sonra hafıza temizleniyor ve diğer 100’ü alıyor.” böylece çok çok az hafıza kullanmış olacağız. Chunk metodu veritabanından verileri 100’er parçalar şeklinde alarak işlem yapmamızı sağlıyor olacak.

Chunk metodu kullanırken sorguda filtreleme yapıyorsak şuna dikkat etmeliyiz:

Mesela aşağıdaki kodda yaptığımız gibi bir kullanımdan kaçınmalıyız. Aşağıdaki kullanım yanlıştır.

User::where('approved', 0)->chunk(100, function ($users) {

  foreach ($users as $user) {

    $user->update(['approved' => 1]);

  }

});

Teknik olarak yukarıdaki kod çalışır ve hata vermez; ama mantık hatamızdan dolayı şöyle bir problemle karşı karşıya kalırız:

Siz normalde approved false olmuş kullanıcıları çekiyorsunuz ve çektiğiniz veride approved true yapıyorsunuz. Laravel burada bir sonraki 100 kayıtı işleme aldığında sorgu filitresini tekrar işletiyor ve değişmiş data üzerinden sorgu yapıyor tekrar. Bu sizin bir sayfa atlamanıza sebebiyet vermiş olacak. Bu şu demek yarı veride bu değişikliği yapmış olacaksınız.

Bu mantık hatasını nasıl düzelteceğiz? Şu şekilde sorgu yaptırarak:

1. Ya filtre yaptığınız alanı update ile değiştirmeyeceksiniz aşağıdaki örnek gibi:

User::where('approved', 0)->chunk(100, function ($users) {

  foreach ($users as $user) {

    $user->update([‘is_paid’ => 1]);

  }

});

2. Ya da illa da filtre yaptığınız alanı değiştirecekseniz şöyle değiştireceksiniz:

İlk başta filtre yapmadan chunk ile tüm kaydı bölerek çekeceksiniz. Sonra da döngü içinde if ile filtre durumunu kontrol edip güncelleme yapabilirsiniz.

User::chunk(100, function ($users) {

  foreach ($users as $user) {

    if ($user->approved == 0) {

        $user->update(['approved' =>1]);

    } 

   }

});

Yeni EK: (22 Aralık 2018)

3. “chunkById” metodu ile filtre yaparak da bu sorunu aşabiliriz. Böylece filtre yapılan alan ile update yapılan alan aynı olabilir:

User::where('approved', 0)->chunkById(100, function ($users) {

  foreach ($users as $user) {

    $user->update(['approved' => 1]);

  }

});

Etiketler: