MONGODB SCHEMA DESİGN ANTİ-PATTERN’LERİ: PERFORMANSI DÜŞÜREN HATALAR
MongoDB “şemasız” değildir; sadece şemayı uygulama tarafına ve veri modelleme kararlarına bırakır. Bu özgürlük doğru kullanıldığında esneklik ve hız kazandırır, yanlış kullanıldığında ise sorguların yavaşlaması, indekslerin şişmesi ve maliyetli güncellemelerle geri döner. Özellikle üretimde veri büyüdükçe, küçük gibi görünen bir şema kararı gecikme sürelerini katlayarak artırabilir.
Bu yazıda, MongoDB şema tasarımında en sık görülen anti-pattern’leri (performansı düşüren hatalı kalıpları) gerçekçi örneklerle ele alacağız. Amacımız “tek doğru model” önermek değil; sorgu kalıpları, güncelleme yükü ve büyüme davranışı üzerinden karar vermeyi kolaylaştırmak. İyi şema tasarımı, çoğu zaman iş yükünü (read/write dağılımı) ve erişim paternlerini merkeze alan bir optimizasyondur.
Okurken şunu akılda tut: MongoDB’de performans genellikle üç şeyde kaybolur: gereksiz veri tekrarları, kontrolsüz doküman büyümesi ve yanlış indeksleme. Şimdi bu hataları tek tek inceleyelim.
Primary Keyword: MongoDB şema tasarımı anti-pattern’leri neden kritik?
Anti-pattern ne demek, pratikte nasıl görünür?
Anti-pattern, ilk bakışta pratik görünen ama veri büyüdükçe sisteme gizli maliyet bindiren tasarım tercihidir. Örneğin “her şeyi tek dokümana gömelim” yaklaşımı başlangıçta tek sorguda her şeyi getirdiği için hızlı görünür; ancak zamanla doküman boyutu büyür, güncellemeler pahalılaşır, indeksler ağırlaşır ve replikasyon trafiği artar.
Performans kaybını tetikleyen üç sinyal
- Okuma sorguları için fazla alan taşımak (gereksiz alanlar, büyük alt dokümanlar)
- Yazma işlemlerinde write amplification (küçük değişiklik için büyük dokümanın tekrar yazılması)
- İndekslerin gereksiz büyümesi ve RAM’de tutulamaması
Bu sinyaller çoğu ekipte önce “anomali” gibi görünür: ara sıra yavaş sorgu, zaman zaman CPU artışı, arada bir “neden disk arttı?” sorusu… Oysa kökte genellikle şema tasarımında bir anti-pattern vardır.

Sınırsız büyüyen diziler: Unbounded Arrays tuzağı
Neden tehlikeli?
Bir dokümanın içinde kullanıcı etkinlikleri, loglar, yorumlar, bildirimler gibi zamanla sınırsız büyüyen diziler tutmak yaygın bir hatadır. Başta “kullanıcı dokümanını getirince her şey hazır” gibi görünür; ancak dizi büyüdükçe doküman boyutu artar, güncellemeler maliyetlenir ve belli bir noktadan sonra okuma performansı düşer. Üstelik büyüme davranışı tahmin edilemez hale gelir.
Belirti: Küçük güncelleme, büyük yazma
Örneğin sadece bir yorum eklemek için dokümanın büyük kısmı yeniden yazılabilir. Bu, IO baskısı ve replikasyon gecikmesi yaratır. Dahası, uygulama yanlışlıkla dizinin tamamını çekiyorsa ağ trafiği de şişer. Bu durum özellikle mobil istemcilerde belirginleşir.
İyi alternatifler
Sınırsız listeleri ayrı koleksiyonda tutup referanslamak, son N kaydı gömme (rolling window) veya özet alanlar tutmak gibi yaklaşımlar daha güvenlidir. Örneğin kullanıcı profiline yalnızca son 20 aktiviteyi gömüp, geri kalanını ayrı koleksiyonda saklamak çoğu kullanımda yeterlidir.
{
"_id": "user_123",
"name": "Erhan",
"recentActivities": [
{ "ts": "2026-01-30T10:11:00Z", "type": "login" },
{ "ts": "2026-01-30T10:12:10Z", "type": "view", "itemId": "p_77" }
],
"activitySummary": {
"lastLoginAt": "2026-01-30T10:11:00Z",
"views30d": 128
}
}Detay aktiviteler ayrı koleksiyona taşınır:
{
"_id": "act_9981",
"userId": "user_123",
"ts": "2026-01-30T10:12:10Z",
"type": "view",
"itemId": "p_77"
}Bu tasarım, büyümeyi yatay ölçeklenebilir hale getirir ve sorguları daha kontrollü yapmanı sağlar. Ayrıca “recentActivities” gibi alanlar, kullanıcı profili ekranı için tasarlanmış bir cevap üretir; ham veriyi taşımak yerine ihtiyacı karşılar.
Aşırı embedding: Her şeyi tek dokümana gömmek
Embedding ne zaman doğru?
Embedding, birlikte okunan ve birlikte değişen veriler için güçlü bir tekniktir. Sipariş ve sipariş kalemleri gibi “atomik” bir bütün oluşturan ve çoğu zaman birlikte getirilen verilerde iyi çalışır. Ancak her ilişkide embedding kullanmak, zamanla “mega doküman” problemine dönüşebilir.
Anti-pattern: Nadir kullanılan alanları da taşımak
Bir ürün dokümanına; yorumlar, fiyat geçmişi, stok hareketleri, kampanya logları gibi farklı erişim paternlerine sahip tüm alt verileri gömmek, okuma sorgularını ağırlaştırır. Ürün listeleme ekranı sadece isim/fiyat/puan isterken, uygulama yanlışlıkla bu büyük dokümanı çekerse gecikme kaçınılmaz olur. Burada problem sadece doküman boyutu değil, “yanlış payload” taşımaktır.
Pratik kural: Okuma ekranına göre modelle
En çok kullanılan ekranı/endpoint’i düşün: listeleme mi, detay sayfası mı, admin raporu mu? Şema tasarımını, en sık kullanılan sorgulara göre optimize etmek genellikle en iyi geri dönüşü verir. Nadir kullanılan “audit” verilerini ayrı koleksiyona taşımak, sıcak path’i rahatlatır.
Aşırı referencing: Her şeyi ayrı koleksiyona bölmek
Belirti: Uygulamada çoklu sorgu zinciri
Diğer uçta ise “her şey normalize olsun” yaklaşımı vardır. MongoDB’de ilişkileri yönetmek elbette mümkün; ancak her okuma için 4–5 koleksiyona gidip gelmek, özellikle yüksek gecikmeli ortamlarda pahalıdır. Bu durum uygulamada “önce kullanıcıyı çek, sonra profilini çek, sonra rolünü çek, sonra izinlerini çek…” gibi zincire dönüşür.
N+1 sorgu problemi
Listeleme ekranında 100 kullanıcı gösteriyorsan ve her kullanıcı için ayrı bir profili çekiyorsan, bir anda 101 sorgu üretebilirsin. Bu anti-pattern, veritabanını sorgu sayısıyla boğar. Burada ya profilin gerekli alanlarını kullanıcı dokümanına gömmek ya da toplu sorgulama ve önbellek stratejisi kullanmak gerekir.
Denge: Hibrit yaklaşım
MongoDB’de çoğu gerçek sistem hibrittir: sık okunan özet alanları göm, nadir ve ağır alt veriyi referansla. Böylece hem tek sorguda hızlı yanıt alır, hem de büyümeyi kontrol edersin.
Yanlış indeks stratejisi: “İndeks var, demek ki hızlı” yanılgısı
Anti-pattern: Her alana indeks eklemek
İndeks, okuma performansı için kritiktir; ancak her indeks yazma maliyeti demektir. Çok indeksli koleksiyonlarda insert ve update işlemleri belirgin şekilde yavaşlayabilir. Ayrıca indeksler disk ve RAM tüketir. Eğer çalışma seti RAM’e sığmıyorsa, beklenmedik gecikmeler görürsün.
Anti-pattern: Sorgu kalıbıyla uyumsuz bileşik indeks
Örneğin sorguların çoğu { tenantId, status, createdAt } üzerinden çalışıyorsa; buna uygun bileşik indeks gerekir. Sadece “createdAt” indekslemek çoğu zaman yetmez. Sorgunun filtreleme ve sıralama düzenini analiz etmeden indeks eklemek, “indeks var ama kullanılmıyor” durumunu doğurur.
İyi pratik: En sık sorgudan geriye doğru tasarla
Sorgu örneği:
db.orders.find(
{ tenantId: "t_1", status: "PAID", createdAt: { $gte: ISODate("2026-01-01") } }
).sort({ createdAt: -1 }).limit(50)Bu sorgu için genellikle aşağıdaki indeks anlamlıdır:
db.orders.createIndex({ tenantId: 1, status: 1, createdAt: -1 })Bu, hem filtrelemeyi hem sıralamayı destekler. Burada önemli detay şudur: indeks tasarımı, “genel doğrular” yerine sorgu paternleri üzerinden yapılır. Bu yüzden önce en yoğun endpoint’leri çıkarıp, onları hedeflemek çoğu zaman en hızlı kazanımı sağlar.
Document Growth: Güncellemelerle şişen dokümanlar
Büyüme davranışı neden önemlidir?
MongoDB dokümanları güncellemelerle büyüyebilir. Özellikle aynı dokümanda sürekli yeni alan eklemek (örneğin dinamik key’ler, tarih bazlı alan adları) veya alt dokümanları genişletmek, dokümanın beklenmedik şekilde şişmesine yol açar. Bu durum hem depolama hem de performans açısından sürpriz yaratır.
Anti-pattern: Dinamik alan adlarıyla metrik tutmak
Örnek kötü yaklaşım: Her gün için ayrı bir alan adı üretmek. Bu tasarım, indekslemeyi zorlaştırır ve dokümanı zamanla şişirir. Onun yerine metrikleri ayrı koleksiyonda zaman serisi gibi tutmak veya sabit şemalı bir dizi kullanmak daha sağlıklıdır.
İyi pratik: Özet + ayrıntı ayrımı
Dokümanda sadece özet metrikleri tutup, ayrıntıyı ayrı koleksiyonda saklamak büyümeyi kontrol eder. Ayrıca bu yaklaşım, raporlama sorgularını da daha okunabilir hale getirir. Buradaki kritik fikir şudur: her veri aynı “sıcaklıkta” değildir; bazı veriler sık okunur, bazıları sadece analiz için tutulur.
Sharding ve “hot partition” riski: Yanlış shard key seçimi
Belirti: Tek shard’a yığılma
Yatay ölçeklemede en can yakıcı anti-pattern’lerden biri, shard key’i yanlış seçmektir. Örneğin monoton artan bir değer (zaman damgası gibi) üzerinden shard key belirlemek, yeni yazmaların belirli bir aralıkta toplanmasına ve “hot partition” oluşmasına neden olabilir. Sonuç: bazı shard’lar aşırı yüklenirken diğerleri boşta kalır.
İyi shard key kriterleri
- Yükü dağıtabilmeli (yüksek kardinalite)
- En sık sorgularda kullanılmalı
- Yazma ve okuma dağılımını dengeli tutmalı
Tenant bazlı sistemlerde dikkat
Çok kiracılı (multi-tenant) sistemlerde tenantId iyi bir aday gibi görünse de, “büyük tenant” problemi doğabilir. Çok büyük bir tenant tüm yükü tek shard’a taşıyorsa, tenantId tek başına yeterli olmayabilir. Bu durumda bileşik bir strateji, ek dağıtıcı alanlar veya farklı partition yaklaşımı gerekebilir. Burada tek doğru yok; iş yükü profili belirleyicidir.

Anti-pattern kontrol listesi: Üretime çıkmadan önce hızlı test
Sorulacak sorular
- En sık 3 endpoint’in sorgu filtreleri ve sıralama alanları nedir?
- Dokümanlar zamanla büyüyor mu, büyüyorsa hangi alanlar nedeniyle?
- Bir update operasyonu ortalama kaç bayt veri değiştiriyor, pratikte ne kadar yazma yaratıyor?
- İndeksler çalışma seti olarak RAM’e sığıyor mu?
- Listeleme ekranlarında N+1 sorgu üreten bir akış var mı?
Bu sorulara net cevap veremiyorsan, şema tasarımında performans riski vardır. Özellikle “doküman büyümesi” çoğu ekipte gözden kaçar; çünkü ilk günlerde veri azdır ve sorun görünmez.
Sonuç: Doğru şema, doğru iş yükü demektir
MongoDB şema tasarımında performansı belirleyen şey, teorik normalizasyon kuralları değil; gerçek sorgu paternleri, güncelleme yoğunluğu ve verinin büyüme davranışıdır. Unbounded array’lerden kaçınmak, embedding/referencing dengesini kurmak, indeksleri sorgu kalıplarına göre seçmek ve sharding kararlarını yük profiliyle doğrulamak, üretimde büyük fark yaratır.
Şema tasarımını derinleştirmek ve gerçek senaryolar üzerinden pratik yapmak istersen, MongoDB Eğitimi sayfasına göz atabilirsin. Orada veri modelleme, indeksleme ve performans analizini birlikte ele alan bir akış bulacaksın.
İpucu: Şema tasarımını “bugün çalışıyor” diye onaylama; “6 ay sonra 10 kat veriyle de çalışır mı?” sorusunu mutlaka sor. Bu bakış açısı, anti-pattern’leri daha ortaya çıkmadan yakalamanı sağlar.


