MONGODB INDEX VE AGGREGATION
Pazartesi sabahı dashboard açılmıyor. Geliştirici explain çıktısına bakıyor: aggregation pipeline 30 saniye sürüyor, üstüne COLLSCAN yazıyor. Aynı sorgu staging'de 200 ms'di — production'da koleksiyon 12 milyon dokümana ulaşmış. Sorun pipeline'ın yanlış yazılmış olması değil; sadece index'lerle uyumsuz sırada yazılmış. Aggregation framework güçlü bir araç ama her stage'in index ile konuşma kuralları var. Bu kuralları bilmeyince motorun tamamı her belgeyi disk'ten okumak zorunda kalıyor.
COLLSCAN ile IXSCAN Arasındaki Fark
Explain çıktısında gördüğünüz winningPlan.stage alanı, MongoDB'nin sorguyu nasıl çözdüğünü söyler. COLLSCAN her belgeyi tek tek okuduğu anlamına gelir; küçük koleksiyonlarda fark etmez ama milyon satırdan sonra darboğaza dönüşür. IXSCAN ise B-tree üzerinde sıçrayarak yalnızca eşleşen anahtarlara gider. Aggregation pipeline'ı yazarken hedef, ilk stage'in IXSCAN üretmesidir.
- COLLSCAN: Index yok ya da pipeline index'e izin vermiyor — tüm koleksiyon taranır.
- IXSCAN: Sorgu bir index prefix'i ile eşleşiyor — sadece ilgili anahtarlar okunur.
- FETCH: Index üzerinde bulunan kayıt için tam belge diskten getirilir; covered query'de bu stage olmaz.
- SORT (in-memory): Index ile karşılanmayan sıralama; 32 MB sınırı aşılırsa hata fırlatır.
Pipeline Stage Sıralaması Performansı Belirler
Aggregation framework'ün altın kuralı şudur: $match ve $sort mümkün olduğunca öne alınmalı. MongoDB optimizer bazı durumlarda sırayı kendisi düzeltir ama her senaryoda değil. $project veya $addFields'tan sonra gelen $match, hesaplanmış alanlara baktığı an index kullanamaz hale gelir. Aynı şey $lookup sonrası gelen filtrelemeler için de geçerli — birleştirme önce yapılır, sonra her ara belge için filtre çalıştırılır.

Pratik kural: filtreyi pipeline'ın ilk satırına yaz, sıralamayı hemen ardından koy, projeksiyonu en sona bırak. Böylece motor önce index üzerinden eleme yapar, sonra geriye kalan az sayıda belge üzerinde ağır işleri yürütür.
Bileşik Index ve ESR Kuralı
Tek alanlı index çoğu zaman yetmez. Bir sorgu hem status alanına eşitlik uyguluyor, hem createdAt'a göre sıralıyor, hem de price aralığı filtreliyorsa tek index'le çözmek için ESR kuralı devreye girer: Equality, Sort, Range sırasıyla alanları index'e koy.
- Equality: Eşitlik kontrolü yapılan alanlar en sola yazılır (örn.
status: "active"). - Sort: Sıralanacak alan ortada yer alır (örn.
createdAt: -1). - Range: Aralık operatörleri (
$gt,$lt,$in) en sağa konur.
Bu sıraya uymayan bir index, sorgunun bir kısmını karşılasa bile sıralama için SORT stage'ini in-memory olarak tetikler. Index tiplerinin ayrıntılarını ve davranışlarını öğrenmek için resmi dokümantasyonu incelemek faydalı olur. MongoDB temellerini pekiştirmek için kapsamlı bir MongoDB eğitimi içeriğinden yararlanabilirsiniz.
$match'i Pipeline'ın Başına Çekmek
Optimizer, $project stage'inden önce gelen $match'i otomatik olarak öne taşır — ancak yalnızca filtre, projeksiyonda yeniden adlandırılmamış alanlara değiniyorsa. Şu örnek index kullanamaz çünkü fullName hesaplanmış bir alandır:
$addFields: { fullName: { $concat: ["$firstName", "$lastName"] } }ardından$match: { fullName: "..." }→ COLLSCAN.$match: { firstName: "Ali", lastName: "Yılmaz" }ardından$addFields→ IXSCAN mümkün.
Yani filtre, ham belge alanları üzerinde çalışmalıdır. Hesaplanmış alanlara filtre uygulamak gerekiyorsa, ilk önce ham alanlarla kabaca daraltıp ardından ince filtreyi uygulamak iki adımlı bir çözüm sunar.
$lookup ve Index Stratejisi
İlişkisel join'in MongoDB karşılığı olan $lookup, sağdaki koleksiyonun foreignField'ında index olmadığında her belge için COLLSCAN tetikler. Bu, soldaki belge sayısı kadar tam koleksiyon taraması demektir — 10.000 sipariş x 50.000 ürün = 500 milyon karşılaştırma. Performans testlerinde foreignField'a index eklendikten sonra aynı pipeline'ın 28 saniyeden 400 ms'ye düştüğü gözlemlenir.

$lookup sonrasında gelen $match'ler, ana belge alanlarına dokunuyorsa optimizer onları öne çekmeyi dener. Ama join sonucu üretilen array alanına filtre uyguluyorsanız bu öne çekme mümkün olmaz — array'i filtrelemek için $lookup içindeki pipeline parametresini kullanmak daha verimli olur.
Covered Query: FETCH Stage'ini Atlamak
Eğer sorgunun ihtiyaç duyduğu tüm alanlar index içinde varsa MongoDB belgeyi diskten getirmeye gerek duymaz; cevabı doğrudan index'ten döndürür. Buna covered query denir ve aggregation'da en hızlı senaryodur. Koşul: hem filtre alanları hem $project ile döndürülen alanlar index'te bulunmalı, _id projeksiyondan açıkça çıkarılmalıdır.
Pratikte rapor sorguları, sayım sorguları ve facet sorguları covered query'ye uygundur. Detay belgesi çekmeyen tüm aggregation'lar için $project'i bilinçli kurarak FETCH stage'ini elimine etmek 5-10 kat hız kazandırabilir.
Explain ve allPlansExecution Modu
Bir pipeline'ı tanımak için explain'i "executionStats" veya "allPlansExecution" modunda çalıştırmak gerekir. nReturned, totalKeysExamined ve totalDocsExamined üçlüsünü karşılaştırın — ideal olarak ilki ile son ikisi yakın olmalıdır. Eğer totalDocsExamined, nReturned'in onlarca katıysa index seçimi yanlış demektir.
- nReturned: Pipeline'dan dönen belge sayısı.
- totalKeysExamined: Index üzerinde dolaşılan anahtar sayısı.
- totalDocsExamined: Diskten okunan belge sayısı.
- executionTimeMillis: Stage başına geçen süre — yavaş stage burada görünür.
Birden fazla aday index olduğunda allPlansExecution, kaybeden planı da gösterir; bu sayede optimizer'ın neden o index'i seçtiği anlaşılır. Pipeline çıktınızı kalıcı olarak hızlandırmak istiyorsanız, üretim ortamında çalışan en sık sorguların hepsini bu modda profillemek başlangıç noktasıdır.

30 saniyelik pipeline'ın 200 ms'ye inmesi sihir değil; sıralamayı düzeltmek, doğru bileşik index'i kurmak ve covered query fırsatlarını yakalamaktan ibaret. MongoDB performans tuning konusunda derinleşmek için MongoDB eğitimi içeriğini inceleyebilirsiniz. Her aggregation'ın explain çıktısını gözden geçirmek alışkanlık haline geldikçe, production'da geceyarısı uyandıran sorguların sayısı belirgin biçimde azalır.



