MongoDB'de veri modellemenin ana sorusu, ilişkili veriyi aynı belgeye gömmek (embedding) mi yoksa ayrı koleksiyonda tutup referansla bağlamak (referencing) mi gerektiğidir. Belgelerin koyduğu temel ilke şudur: birlikte erişilen veri birlikte saklanmalıdır. Uygulamanız iki parçayı hep beraber okuyor ve güncelliyorsa gömün; alt taraf sınırsız büyüyorsa, sık değişen veri çoğaltılacaksa veya alt kayıt kendi başına sorgulanıyorsa referans kullanın.

Bu yazıda MongoDB resmî belgelerindeki modelleme rehberini Türkçe olarak özetliyor, karar tablosunu, çoğaltılmış veri türlerini, bire çok ilişkide referansın nereye yazılacağını, tasarım desenlerini ve anti-desenleri küçük örneklerle anlatıyoruz. Terimlerin SQL karşılıkları için SQL karşılaştırma yazısına bakabilirsiniz.

Esnek şema ne demek?

MongoDB'nin esnek veri modeli, aynı koleksiyondaki belgelerin aynı alanlara veya aynı veri türlerine sahip olmasını zorunlu kılmaz. Uygulamanız değiştikçe yeni alanlar ve içerik türleri, mevcut belgeleri yeniden yazmadan eklenebilir. Gerektiğinde ise schema validation ile sabit bir yapı zorlanabilir.

Esneklik, plansızlık anlamına gelmez. Belgeler şemayı geliştirme sürecinin erken bir aşamasında planlamanızı ve uygulama büyürken yinelemeli olarak iyileştirmenizi önerir. Önerilen tasarım süreci dört adımdan oluşur: iş yükünü (en sık çalışan işlemleri) belirlemek, ilişkileri haritalamak, tasarım desenlerini uygulamak ve sık sorguları destekleyen indeksleri oluşturmak.

İlişki türleri

İlişki türleri: Bire bir, Bire çok, Çoka çok
  • Bire bir: her belge tam olarak bir başka belgeyle ilişkilidir; örneğin bir hastanın tek bir tıbbi kaydı vardır.
  • Bire çok: bir belge birden çok belgeyle ilişkilidir; örneğin bir kullanıcının çok sayıda yazısı veya yorumu olur.
  • Çoka çok: her iki taraf da birden çok belgeyle ilişkilidir; örneğin bir öğrenci birden çok derse kayıtlıdır, her derste de birden çok öğrenci vardır.

Alan değerleri gömülü belgeler, diziler ve belge dizileri dahil herhangi bir BSON türü olabildiği için bu ilişkilerin hepsi gömme veya referans ile modellenebilir. Kullanılabilir türler için BSON veri tipleri yazısına bakın.

Embedding: ilişkili veriyi tek belgede tutmak

Gömülü modelde ilişkili veri tek bir belge yapısında durur; belge alt belgeler ve diziler içerebilir. Bu modellere denormalize denir. Belgelere göre sağladığı faydalar şunlardır:

  • Okuma işlemlerinde daha iyi performans
  • İlişkili veriyi tek bir veritabanı işlemiyle getirebilme
  • İlişkili veriyi tek bir atomik yazma işlemiyle güncelleyebilme
javascript
// Gömülü model: adres ve iletişim bilgisi kullanıcı belgesinin içinde
{
   _id: "u42",
   ad: "Deniz",
   iletisim: { eposta: "deniz@example.com", telefon: "0555 000 00 00" },
   adresler: [
      { tur: "ev", sehir: "Bursa" },
      { tur: "is", sehir: "İstanbul" }
   ]
}

Bu belgeyi tek bir findOne() ile bütün ayrıntılarıyla okursunuz ve gömülü alanları nokta gösterimiyle sorgularsınız (örneğin "adresler.sehir": "Bursa"). Gömülü dizileri sorgulama ve güncelleme teknikleri dizi sorgulama ve dizi güncelleme yazılarında anlatılıyor.

Küçük belgeleri bir araya toplamak

Çok sayıda küçük belgeden oluşan bir koleksiyonunuz varsa ve bu belgeleri mantıksal bir gruba göre sık sık birlikte okuyorsanız, belgeler onları gömülü belge dizisi içeren daha büyük belgelerde toplamayı (roll-up) önerir. Böylece bir grubu okumak sıralı okumalarla ve daha az rastgele disk erişimiyle yapılır, ortak alanların kopyaları ve indeks girdileri azalır. Ancak çoğu zaman grubun yalnızca bir kısmına ihtiyaç duyuyorsanız bu yöntem fayda sağlamayabilir; ayrı küçük belgeler verinin doğal modeliyse o modeli koruyun.

Referencing: veriyi ayrı koleksiyonlarda bağlamak

Referans modelinde bir belge, başka bir belgeye işaret eden bir değer taşır ve uygulama bu referansı çözerek ilişkili veriye ulaşır. Veri çoğaltılmadan birden fazla koleksiyona bölündüğü için bu modele normalize denir. Belgeler çoğu kullanım için denormalize modelin uygun olduğunu, ancak şu durumlarda referansı düşünmek gerektiğini söyler:

  • Gömme veriyi çoğaltacak ama çoğaltmanın maliyetini karşılayacak kadar okuma avantajı sağlamayacaksa; örneğin gömülü veri sık değişiyorsa.
  • Karmaşık çoka çok ilişkileri veya büyük hiyerarşik veri kümelerini temsil etmeniz gerekiyorsa.
  • İlişkili varlığı sık sık kendi başına sorgulamanız gerekiyorsa.

Normalize veriyi sorgularken birden fazla koleksiyonu $lookup veya $graphLookup aşamasıyla birleştirirsiniz. Unutmayın ki referans modelinde ilişkili parçaları okumak ve değiştirmek için uygulama ayrı okuma ve yazma işlemleri yapmak zorundadır.

Karar tablosu

Karar tablosu: İlişkili veriyi bir arada tutmak modeli ve kodu basitleştiriyor, Varlıklar arasında sahiplik veya içerme (has-a, contains) ilişkisi var, Uygulama parçaları birlikte...

Belgelerdeki karar tablosunun Türkçe özeti aşağıdadır. Bir senaryo birden fazla satıra uyuyorsa ağır basan erişim desenini esas alın:

SenaryoYöntem
İlişkili veriyi bir arada tutmak modeli ve kodu basitleştiriyorEmbedding
Varlıklar arasında sahiplik veya içerme (has-a, contains) ilişkisi varEmbedding
Uygulama parçaları birlikte sorguluyorEmbedding
Veri sıklıkla birlikte güncelleniyorEmbedding
Veri aynı anda arşivlenmeliEmbedding
İlişkinin alt tarafının kardinalitesi yüksekReferencing
Çoğaltılmış veriyi yönetmek çok karmaşık ve istenmiyorReferencing
Birleşik veri boyutu uygulama için fazla bellek veya bant genişliği tüketiyorReferencing
Gömülü veri sınırsız büyüyorReferencing
Yazma ağırlıklı iş yükünde veriler farklı zamanlarda yazılıyorReferencing
Alt taraftaki veri ebeveyn olmadan da var olabiliyorReferencing

Bire çok ilişkide referans nereye yazılır?

Belgelerdeki yayınevi ve kitap örneği bu sorunun cevabını verir: ilişkinin nasıl büyüdüğü, referansın nerede saklanacağını belirler. Yayınevi başına kitap sayısı az ve büyümesi sınırlıysa kitap referanslarını yayınevi belgesindeki bir dizide tutabilirsiniz. Kitap sayısı sınırsızsa bu yaklaşım değişken ve sürekli büyüyen diziler üretir; bunun yerine yayınevi referansını her kitap belgesine yazmak gerekir.

javascript
// Önerilen: referans "çok" tarafında
db.yayinevleri.insertOne( { _id: "deniz-yayin", ad: "Deniz Yayınları", sehir: "İzmir" } )

db.kitaplar.insertMany( [
   { _id: 1001, baslik: "Kıyı", sayfa: 210, yayineviId: "deniz-yayin" },
   { _id: 1002, baslik: "Liman", sayfa: 184, yayineviId: "deniz-yayin" }
] )

// Bir yayınevinin kitapları
db.kitaplar.find( { yayineviId: "deniz-yayin" } )

Bu sorgunun hızlı olması için kitaplar koleksiyonunda yayineviId alanına indeks eklemeniz gerekir. Örnekte yayınevinin _id değeri olarak okunabilir bir dize kullandık; _id benzersiz olduğu sürece ObjectId dışında bir değer olabilir.

Veri çoğaltma: ne zaman sorun, ne zaman çözüm?

Gömme çoğu zaman bir miktar veri çoğaltma demektir. Belgeler çoğaltmadan önce iki şeyi tartmanızı ister: join'leri ortadan kaldırarak okumada ne kadar kazanç sağladığı ve çoğaltılan verinin ne sıklıkla güncellenmesi gerektiği. Seyrek güncellemeleri yönetmenin ek mantığı, her okumada join yapmaktan daha ucuzdur; ama çok sık güncellenen çoğaltılmış veri ağır iş yükü ve performans sorunları doğurabilir.

Çoğaltılan verinin türüAnlamıÖrnek
DeğişmezHiç değişmeyen veri; çoğaltmaya çok uygunHesabın açılış tarihi
ZamansalDeğişebilir ama geçmişteki değeri korunmalıSipariş anındaki teslimat adresi
Bayatlığa duyarlıTüm kopyalar sık güncellenmeli; transaction veya tetikleyici gerekebilirÜrünün stok adedi
Bayatlığa duyarsızBir süre eski kalabilir; arka plan işiyle güncellenebilirMüşteriye önerilen ürünler listesi

Mini örnek: sipariş anındaki ürün bilgisi

Belgelerdeki anti-desen sayfası, her sipariş okumasında ürünleri $lookup ile getirmek yerine ürün ayrıntılarının bir alt kümesini siparişe gömmeyi önerir. Fiyat ve ürün adı sipariş anına ait zamansal veridir: ürünün fiyatı sonradan değişse bile eski siparişin tutarı değişmemelidir. Bu yüzden çoğaltma burada bir hata değil, doğru modeldir.

javascript
db.siparisler.insertOne( {
   _id: 5001,
   musteriId: "u42",
   tarih: new Date("2026-11-05"),
   kalemler: [
      { urunId: 7, ad: "Kablosuz Fare", birimFiyat: 450, adet: 2 },
      { urunId: 9, ad: "USB Bellek", birimFiyat: 180, adet: 1 }
   ],
   toplam: 1080
} )

Ürünün açıklaması, üreticisi gibi siparişe ait olmayan ayrıntılar urunler koleksiyonunda kalır. Sipariş sayfası tek bir okuma ile çizilir; ürün sayfası ise ürün koleksiyonundan okunur. Stok adedi ise bayatlığa duyarlı bir veridir ve siparişe kopyalanmamalıdır.

Tutarlılığı sağlamak

Çoğaltılmış veriyi tutarlı tutmak için belgeler üç yol sayar: ilişkili veriyi gömmek, birden çok koleksiyonu tek atomik işlemde güncelleyen transaction kullanmak ve Atlas Database Triggers (yalnızca Atlas'ta). Transaction'ların okuma çekişmesi nedeniyle performans etkisi yüksek olabilir; gömme ise belge boyutu ve indekslere bağlı olarak düşük ile orta etkilidir. Kendi sunucunuzda Atlas tetikleyicileri olmadığı için, bayatlığa duyarsız veriler için belgelerin önerdiği yolu, yani kopyaları düzenli aralıklarla güncelleyen bir arka plan işini kullanabilirsiniz.

Atomiklik

MongoDB'de yazma işlemleri tek belge düzeyinde atomiktir: bir güncelleme birden çok alt belgeyi etkilese bile ya hepsi güncellenir ya da işlem tamamen başarısız olur. Gömülü model bu nedenle atomik güncellemeyi doğal olarak sağlar. Referanslı modelde birden fazla belgeyi birlikte değiştirmeniz gerekiyorsa transaction'a ihtiyaç duyarsınız. Sipariş ve kalemlerini tek belgede tutmak, "kalemler eklendi ama toplam güncellenmedi" türünden yarım kalmış durumları ortadan kaldırır.

Tasarım desenleri

Tasarım desenleri: Hesaplanmış değerler, Veriyi gruplama, Polimorfik veri, Sürümleme

Belgeler, uygulamanın veriyi nasıl sorguladığına göre seçilecek şu desenleri tanımlar:

  • Hesaplanmış değerler: hesaplamaları önceden veritabanında yapıp istemci istediğinde sonucu hazır tutmak. Bunu otomatikleştirmenin bir yolu $merge ile materialized view kurmaktır.
  • Veriyi gruplama: verileri serilere bölerek performansı artırmak; alt küme (subset), kova (bucket), aykırı değer (outlier) ve öznitelik (attribute) desenleri bu başlık altındadır.
  • Polimorfik veri: farklı alanlara sahip belgeleri tek koleksiyonda tutmak; örneğin farklı spor dallarındaki sporcular veya e-kitap, basılı ve sesli kitap biçimleri.
  • Sürümleme: değişen teknik gereksinimlere karşı şema değişikliklerine hazırlanmak.
  • Arşivleme: eski veriyi ayrı bir yere taşıyarak sık erişilen verinin performansını korumak.
  • Tek koleksiyon: farklı türdeki ilişkili belgeleri referanslarla tek koleksiyonda gruplamak.

Alt küme deseni özellikle yaygındır: bir ürünün bütün yorumları yerine en son birkaç yorumu ürün belgesine gömülür, geri kalanı ayrı bir koleksiyonda tutulur. Kova deseni ise uzun serileri gruplara böler; belgelerdeki örnek, her sayfada 10 işlem gösteren bir uygulama için işlemleri müşteri bazında onarlı gruplar halinde saklar. Zaman serisi verisi için belgeler bu deseni otomatik uygulayan time series koleksiyonlarını önerir.

Kaçınılması gereken anti-desenler

Anti-desenSorunÇözüm yönü
Sınırsız dizilerBelge 16 MB sınırını aşabilir, indeks performansı düşerAlt küme deseni veya referans
Çok fazla koleksiyonDepolama motoru performansı düşebilirKoleksiyon sayısını azaltmak
Gereksiz indekslerEk disk alanı ve daha yavaş yazmaKullanılmayan indeksleri kaldırmak
Şişkin belgelerÇalışma kümesi belleğe sığmazBirlikte okunmayan alanları ayırmak
Çok fazla $lookupSorgu karmaşıklığı artar, performans düşerBirlikte okunan veriyi birlikte saklamak

Sınırsız dizi örneği belgelerde bir kitapçı uygulamasıdır: her yeni yorum kitabın reviews dizisine eklenir ve dizi zamanla aşırı büyür. Uygulama kitap başına yalnızca üç yorum gösteriyorsa, üç yorumu gömüp geri kalanını ayrı bir koleksiyonda tutmak sorunu çözer. Şişkin belge örneğinde ise ana sayfada yalnızca başlık, yazar ve kapak gösterilirken bütün kitap ayrıntıları aynı belgede durur; çözüm, ana sayfa bilgisini ve ek ayrıntıları iki ayrı koleksiyona bölmektir.

İndeks, donanım ve belge boyutu

  • Sık sorguladığınız, sıraladığınız veya join yaptığınız alanlara indeks ekleyin. Belgelere göre her indeks en az 8 kB veri alanı ister ve yazma işlemlerine bir miktar ek maliyet getirir. Ayrıntılar için index oluşturma yazısı.
  • Daha büyük belgeler daha fazla RAM kullanır. Sorgularınızın yalnızca gereken alanları döndürmesini sağlayarak çalışma kümesinin gereksiz büyümesini önleyin.
  • Çok küçük belgelerde her belgenin sabit ek yükü belirginleşir. _id alanına anlamlı bir değer yazmak ve gerekirse alan adlarını kısaltmak yer kazandırır; belgelerdeki örnekte last_name yerine lname, best_score yerine score kullanmak belge başına 9 bayt kazandırır. Ancak belgeler genel modeli değiştirmenin çoğu zaman daha etkili olduğunu, kısa adların okunurluğu azalttığını ve indeks boyutunu etkilemediğini de not eder.
  • Belirli bir süre sonra silinmesi gereken veriler (oturumlar gibi) için TTL index, yalnızca son eklenen belgelerin kullanıldığı durumlar için capped koleksiyonlar düşünülebilir.

Sık yapılan hatalar

  • İlişkisel şemayı birebir taşımak: her tabloyu bir koleksiyon yapmak çok sayıda $lookup ve transaction ihtiyacı doğurur.
  • Her şeyi gömmek: sınırsız büyüyen yorumlar, loglar veya mesajlar belgeyi şişirir.
  • Sık değişen veriyi çoğaltmak: stok gibi bayatlığa duyarlı verinin her kopyasını güncel tutmak pahalıdır.
  • Erişim desenini bilmeden modellemek: önce en sık çalışan sorguları listeleyin, model onlara göre şekillenir.
  • İndeksleri sonraya bırakmak: referans alanları indekslenmezse referanslı model yavaşlar.

Sık Sorulan Sorular

Çoka çok ilişkiyi nasıl modellemeliyim?

Belgeler karmaşık çoka çok ilişkiler için referansı önerir. Referansın nereye yazılacağına karar verirken bire çok örneğindeki kural yine işe yarar: kimlikleri tutan dizi küçük ve sınırlı kalıyorsa sorun yoktur; sınırsız büyüyebiliyorsa bu, belgelerin sınırsız dizi anti-deseni olarak tanımladığı duruma dönüşür ve modeli yeniden düşünmeniz gerekir.

Şemayı sonradan değiştirmek zor mu?

Belgelere göre esnek model sayesinde yeni alanlar mevcut belgeleri yeniden yazmadan eklenebilir. Belgelerdeki örnekte bir eğitim platformu, dersleri önce kurs belgesine gömer; ders içerikleri videolar ve testlerle çeşitlenince dersleri ayrı bir lessons koleksiyonuna taşıyıp referansla bağlar. Modeli bu şekilde yineleyerek geliştirmek olağandır.

Gömülü belge mi, alt koleksiyon mu daha hızlı?

Belgeler gömülü modelin okumada daha iyi performans sağladığını söyler; ancak belge büyüdükçe bellek kullanımı artar. Kesin cevap için kendi sorgularınızı explain() ile ölçün.

Veri tutarlılığı için her zaman transaction mı kullanmalıyım?

Hayır. Belgeler, çok belgeli transaction'lar yerine çoğu senaryoda doğru modellenmiş gömülü verinin daha uygun kalacağını belirtir. Transaction'ı, gerçekten birden fazla belgeyi birlikte değiştirmeniz gereken durumlara saklayın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MongoDB öğrenme rehberi · $lookup ile join · Schema validation · Transaction kullanımı · SQL karşılaştırması · Veritabanı türleri