MongoDB projeksiyonlarinda dizi elemanlarini $slice ile sinirlamak
Selamlar, bu yazımda MongoDB tarafında çok sık ihtiyaç duyduğumuz ama bir o kadar da gözden kaçan bir konuya bakacağız: bir doküman içindeki uzun dizinin tamamını çekmeden sadece istediğimiz kısmını almak. Konunun adı $slice projection operatörü. Hadi başlayalım.
İlk başlarda MongoDB ile çalışırken, comments veya notifications gibi alanları sorgudan çekerken dizinin hepsi geliyordu ve buna dokunmadan rahatça yaşıyorduk. Sonra bir kullanıcının üstüne otuz bin tane bildirim biriktiğinde işler değişti. Tek bir findOne çağrısı bile listede gözle görülür şekilde ağırlaşıyordu. İşte $slice tam burada işe yarıyor.
$slice projection nedir?
Kısaca: bir sorguda dizi alanının tamamı yerine sadece bir kısmını döndürmemizi sağlayan projeksiyon operatörü. Üç temel formu var:
// Ilk N eleman
{ comments: { $slice: 3 } }
// Son N eleman (negatif deger)
{ comments: { $slice: -3 } }
// skip kadar atla, limit kadar al
{ comments: { $slice: [skip, limit] } }
Burada dikkat edilecek nokta, $slice filtreleme değil, projeksiyon yapıyor. Yani sorgu yine eşleşen tüm dokümanları taramaya devam ediyor, ama sonuçta dizinin sadece belirttiğin parçası dönüyor. Bunu sık karıştıranı gördüm; benim de ilk başlarda kafam karışmıştı.
Pratikte nasil kullaniyoruz?
Diyelim elimizde şöyle bir koleksiyon var:
{
_id: 1,
title: 'MongoDB ipuclari',
comments: ['c1', 'c2', 'c3', 'c4', 'c5']
}
Son iki yorumu istiyorsan:
db.posts.find({}, { comments: { $slice: -2 } })
// donus: comments: ['c4', 'c5']
Sayfa numarasıyla yorum çekmek istiyorsan, [skip, limit] formu cuk oturuyor:
function getCommentPage(postId, page, pageSize) {
return db.posts.findOne(
{ _id: postId },
{ comments: { $slice: [(page - 1) * pageSize, pageSize] } }
);
}
Tabii burada şuna dikkat: dizinin tamamı belleğe alındıktan sonra dilimleniyor. Yani 100 bin elemanlı bir dizide ortadaki 10'u istiyorsanız MongoDB diziyi yine de okuyor. Eğer dizilerin gerçekten patlama riski varsa, yorumları ayrı koleksiyona almak daha sağlıklı; bana sorarsanız üç haneli sayıları geçen yerlerde $slice'a güvenmek yanıltıcı.
Diger projeksiyonlarla birlestirmek
$slice'ı klasik dahil/hariç (1 / 0) projeksiyonuyla rahatça birlikte kullanabiliyoruz:
db.posts.find(
{ status: 'published' },
{
title: 1,
author: 1,
comments: { $slice: 5 },
_id: 0
}
)
Ama aynı alan üzerinde $elemMatch veya pozisyonel $ operatörüyle birleştiremiyoruz. Bu da Mongo dokümantasyonunun arka sayfalarında gizli bir kural; staging'de hiç sorun çıkarmadan geçen bir sorgunun production'da Cannot specify positional operator and $slice hatasını verdiğini görmek mümkün.
Aggregation icindeki $slice
Bir de aggregation pipeline'ında ayrı bir $slice ifade operatörü var, ki bu çok daha esnek:
db.posts.aggregate([
{
$project: {
title: 1,
recentComments: { $slice: ['$comments', -5] },
commentCount: { $size: '$comments' }
}
}
])
Burada hem son 5 yorumu alıyoruz hem de $size ile toplam yorum sayısını döndürüyoruz. Projection formu bunu yapamaz; aggregation formu yapar. Şahsi kanaatim, dinamik dilimleme ya da hesaplama gerekiyorsa doğrudan aggregation'a geçmek daha temiz.
Sik karsilasilan tuzaklar
- Tüm diziyi taradığını unutmak:
$sliceağ trafiğini azaltır, disk okumasını değil. Çok büyük dizilerde performans hâlâ sıkıntılı olabilir. [skip, limit]formunda negatif skip:[-3, 2]ifadesi sondan 3 atlar ve 2 alır. İlk gördüğümde[3, 2]ile karıştırmıştım; doküman dikkatli okunmalı.- Pozisyonel
$ile birlikte kullanmak: Aynı alanda iki array operatörünü çakıştıramazsınız. Çakıştırırsanız sorgu hata verir. - Dizi olmayan alana uygulamak: Hata vermez ama alan olduğu gibi döner. Sessiz bir aldatmaca; testinizde fark etmeniz zor.
Kapanis
$slice küçük ama aklımızda tutmamız gereken bir araç; özellikle bildirim, yorum, son aktiviteler gibi 'sadece son N tanesi lazım' senaryolarında yapay bir aggregation'a girmeden işi halledebiliyoruz. Bence tek ipucu şu: dizilerin gerçekten büyüdüğü yerlerde $slice çözüm değil, ayrı koleksiyona geçmenin habercisidir. Umarım faydalı olur, görüşmek üzere.
