Bir proje geliştirirken illaki ilişkisel tablolar kullanmışızdır. İlişkisel tablolar bizim veri yığınınından kurtulmamızı sağlar. Örneğin facebook User tablosu üzerinde arkadaşlarınızı tutuyor olsa nasıl olurdu ? Gereksiz karmaşık değil mi ? Bir arkadaşınızı silmek istediğinizde sütundaki arkadaş datasını çekip içerisinden arkadaşınızın id’sini bulup arrayden çıkarıp tekrar kaydetmesi gerekecekti. Bu tip karmaşıklıklardan kurtulmak için ilişkisel tablolar kullanıyoruz. Arkadaşlarımız bir tabloda, resimlerimiz bir tabloda, durumlarımız bir tabloda. Peki bu düzen ne gibi sorunları yanında getiriyor ? Evet başlıktan da anlayacağınız üzere N+1 problemini ortaya çıkarıyor. Peki ama nedir bu N+1 problemi ? Şöyle bir örnek ile açıklayacağım ;
Yaklaşık 1 yıl önce tasarım ürünler üreten bir firma için özel taleplerini yerine getirebilecek basit e-ticaret alt yapısı yazdım. Örnek verebilmek için bu sistem üzerinde bulunan 3 tabloyu seçtim.
User : Kullanıcıların/Müşterilerin tutulduğu tablo.
Order : Satış/Sipariş işlemlerinin tutulduğu tablo.
OrderStatus : Satış işleminin durumlarını barındıran tablo. Siparişin aşamalarını barındırmakta. Her Order için veri değil, sistemde bulunan 6 sipariş durumunu barındırır. Bu durumlar siparişlere atanabilir.
Yönetici tarafında bu satışların listelendiği bir ekranımız var, bu ekran üzerinde Satış işleminin yapıldığı müşteri, siparişin sepet tutarı, siparişin en son durumu görüntülenmekte.
Yukarıdaki ekran görüntüsünde Kırmızı renk olan veriler Order tablomdan geliyor, Yeşil olan veriler User tablomdan ve Mavi olan veriler ise OrderStatus tablomdan.
Bu sistemi yazarken ürünün hızlı çıkması için performans alanlarına pek dikkat etmedim. Fırsat bulduktan sonra refactor ederek performans ayarları yaptım. Tahmin edebileceğiniz gibi en büyük performans sorunu n+1 probleminden kaynaklıydı. N+1 probleminin tam olarak yaptığı şu ;
siz order tablosu ile user — orderStatus tablolarına bir ilişki tanımlıyorsunuz (hasMany, hasOne vs… ). Controller kısmından veriyi $Siparisler = Order::all(); şeklinde çekip view katmanıma gönderiyorsunuz. View kısmında bu ilişkiyi kullanırken siparişlerinizin döndüğü foreach döngünüzde $Siparis->getUser->name şeklinde çağırıyorsunuz. Gayet basit bir yöntem. Bu işlemi yaptığımızda her bir döngü elemanında tekrar bir sorgu atarak kullanıcı bilgilerini çekiyor. Sorunun görüntülü şekli tam olarak şu şekilde ;
Yukarıda gördüğünüz gibi, ben sadece 1 sorgu yaptım fakat 32 sorgu oluştu. Bu da 32.09ms içerisinde işlendi. N+1 problemi tam olarak bu, anlatabildiysem ne mutlu.
Şimdi gelelim laravel tarafında bu sorunun çözümüne. Laravel bize çok güzel ve kısa bir yol sunmuş. Nedir bu yol ? With() metodu. Sorgumuzu aşağıdaki gibi düzenliyoruz.
Gördüğünüz gibi artık statuses tabloma sorguyu 1 kere gönderiyorum. Sorgu sayısı 14 azaldı, süre ise 32.09ms den 14.78ms düşmüş oldu. Aynısını User tablosu için de yapalım.
Gördüğünüz gibi sorgu sayısını 4 e indirdik ve süre de 5.51ms oldu. ortalama 27ms bir zaman kazancımız ve 28 sorgu performansı kazancımız oldu.
Başka yazılım yazılarında görüşmek üzere. 👋🏻👋🏻👋🏻