İçeriğe geç

Doküman Modelleme — Göm mü, Referans mı?

Orta 8 dk Sık karşılaşılır

Önce şunu oku: SQL mi, NoSQL mi? — Önce Erişim Desenini Yaz

30 saniyede özet

Doküman veritabanında en büyük karar şu: ilgili veriyi aynı dokümanın içine mi koyacaksın, ayrı tutup bağlantı mı vereceksin? Birlikte okunan ve sınırlı olan içeri girer; sınırsız büyüyen dışarıda durur.

Blog platformu yorumları yazının içine gömdü; tek sorguyla sayfa açılıyordu. Sonra bir yazı viral oldu.

  1. Bayt: Yorumları yazının içine gömdük. Sayfa tek sorguyla açılıyor!

  2. Sen: Kulağa harika geliyor.

  3. Bayt: Sonra bir yazı viral oldu ve yorumlar akmaya başladı...

  4. Bayt: Birlikte okunan birlikte durur. Ama sınırsız büyüyen de mi?

Birlikte okunan birlikte durur

İlişkisel modelde veri önce normalize edilir, sorgu sonra düşünülür. Doküman modelinde sıra terstir: önce uygulamanın veriyi nasıl okuduğuna bakılır.

gömmeDoküman modelinde ilişkili veriyi ayrı bir koleksiyon yerine ana dokümanın içine koymak. Birlikte okunan ve sınırlı veri için tek okuma ve atomik yazma sağlar.Sözlükte gör →, ilişkili veriyi ana dokümanın içine koymaktır. Sipariş ve kalemleri hep birlikte okunur ve birlikte oluşturulur; tek dokümanda durdukları için tek okuma ve tek atomik yazma yeter.

Kafam karıştı, daha basit anlat

Birlikte okuduğun şeyleri aynı zarfa koy. Sipariş ve kalemleri hep birlikte okunuyorsa, tek bir dokümanda durmaları en hızlısıdır.

Hızlı kontrolBaşlangıç

MongoDB'de sipariş kalemlerini siparişin içine gömmenin en büyük kazancı nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınırsız büyüyen gömülmez

Yorumlar yazının içinde bir dizide duruyor. Yazı viral oluyor ve yorum sayısı on binleri geçiyor. Ne olur? Cevabı göster

Doküman büyür ve her yorum onu yeniden yazar. MongoDB’de bir dokümanın üst sınırı 16 MB’tır. Sınıra gelmeden de her okuma ve güncelleme büyük dokümanı taşımak zorunda kalır.

Faturayla ekleri aynı klasöre, bütün yazışmalar asla.
Adım adım oku
  1. Birlikte okunan ve sınırlı kalan veri, yazının başlığı ve etiketleri gibi, aynı dokümanda durur.
  2. Yorumlar da yazının içine bir dizi olarak konur.
  3. Yorumlar sınırsız büyür: her yeni yorum dev dokümanı baştan yazdırır ve doküman boyut sınırına dayanır.
  4. Sınırsız büyüyen veri ayrı bir koleksiyonda tutulur; her yorum yazıya yalnızca bir kimlikle bağlanır.

Kural basit: dizinin üst sınırını söyleyemiyorsan onu gömme. Siparişin kalemleri sınırlıdır; bir yazının yorumları, bir kullanıcının log kayıtları sınırsızdır.

Kafam karıştı, daha basit anlat

Zarfa sonsuza kadar kâğıt eklenebiliyorsa bir gün yırtılır. Kaç tane olacağını bilmediğin şeyleri gömme, ayrı bir yerde tut.

Hızlı kontrolOrta

Bir kullanıcının bütün aktivite log'larını kullanıcı dokümanında bir dizide tutmanın riski nedir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Her ilişkiyi gömmeye mi yoksa ayrı koleksiyona mı daha uygun olduğuna göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Göm

Birlikte okunur, sınırlı, o ana ait.

    Ayrı koleksiyon

    Sınırsız büyür, bağımsız sorgulanır ya da tek güncel doğrusu var.

      Kendin gör

      Göm mü, referans mı?

      Tohum 163480
      { _id, title, body, comments: [ …tüm yorumlar… ] }
      1. Yazı sayfası: yazı ve son 10 yorum
      2. Popüler bir yazıya on binlerce yorum geliyor
      3. Bir kullanıcının bütün yorumları
      Hız
      Adım 0

      Şu an ne oldu?

      Blog yazısı → yorumlar: Göm

      Uygulamanın ihtiyaçları tek tek bu modele karşı denenecek.

      Görevler0/3

      • Sınırsız büyüyen bir diziyi gömaçık

        İpucu

        Popüler bir yazının yorumları.

      • Eski faturaların yeni adresi göstermesine yol açaçık

        İpucu

        Her şeyi tek bir güncel kaynaktan okumak.

      • Yorumlar için bütün ihtiyaçları uygun hâle getiraçık

        İpucu

        Ne tamamen göm, ne tamamen ayır.

      Olay günlüğü (0)

      Henüz olay yok. Oynat veya adımla.

      1. Varsayılanla oynat. Bütün yorumlar gömülü: popüler yazı ve kullanıcı sorguları sorunlu.
      2. Referansa geç. Büyüme sorunu bitti, ama sayfa iki sorgu istiyor.
      3. Karma modeli seç. Son 10 yorum yazıda, gerisi ayrı: hepsi uygun.
      4. Sipariş → müşteri, referans. Eski faturalar yeni adresi gösteriyor.
      5. Aynı ilişkide karma. Ad ve adres kopyalandı, e-posta tek yerde.
      Hızlı kontrolOrta

      Simülatörde sipariş → müşteri ilişkisi tamamen referansla tutulunca fatura ihtiyacı 'sorunlu' çıktı. Neden?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Arada kalan yollar

      Çoğu gerçek model ne tamamen gömülü ne tamamen ayrıdır.

      • subset patternSık okunan sınırlı bir parçayı (ör. son 10 yorum) ana dokümana gömüp tamamını ayrı koleksiyonda tutan doküman modelleme kalıbı.Sözlükte gör →: en sık okunan sınırlı kısmı (son 10 yorum) göm, tamamını ayrı koleksiyonda tut.
      • extended referenceReferansın yanına ilgili dokümanın sık okunan birkaç alanının kopyasını koymak (ör. müşteri id'si ve adı). Okumayı ucuzlatır, kopyaların güncelliğini yönetmeyi gerektirir.Sözlükte gör →: referansın yanına sık okunan birkaç alanın kopyasını koy (müşteri id’si ve adı).
      • Anlık kopya: faturadaki adres, sipariş anının kaydıdır; kopyalanması bir hata değil, bir gerekliliktir.

      Kopyalanan her alan için sor: bu değer değişirse eski kopyalar güncellenmeli mi, yoksa o anın kaydı olarak mı kalmalı?

      Bir banka hesabı bu üç yolu güzelce bir arada gösteriyor. Hesap sahipleri sınırlıdır, hareketler ise yıllarca birikir; bak her biri nereye yerleşiyor.

      Derinleş · Banka hesabı: ne gömülür, ne ayrı durur? 4 dosya · ~53 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      mongo/ accounts.js Hesap dokümanı: sahipler gömülü, son 10 hareket subset olarak burada.

      mongo/accounts.js
      // Read on every app launch: balance, holders and the last few movements in one read.
      db.accounts.insertOne({
      _id: "TR330006100519786457841326", // IBAN
      currency: "TRY",
      balance: NumberDecimal("18250.75"), // Decimal128, never a double
      holders: [ // bounded: an account has one or two holders
      { customerId: "C-1001", name: "Ayşe Yılmaz", role: "PRIMARY" }
      ],
      recentMovements: [ // subset: at most 10, newest last
      { movementId: "M-88412", bookedAt: ISODate("2026-09-24T09:12:00Z"),
      amount: NumberDecimal("-1500.00"), description: "Kira" }
      ],
      schemaVersion: 2
      });

      mongo/ movements.js Hareketler ayrı koleksiyonda; karşı tarafın adı ve IBAN'ı o anın kaydı olarak kopyalanır.

      mongo/movements.js
      // Unbounded: years of movements, so a separate collection linked by accountId.
      db.movements.insertOne({
      _id: "M-88412",
      accountId: "TR330006100519786457841326",
      bookedAt: ISODate("2026-09-24T09:12:00Z"),
      amount: NumberDecimal("-1500.00"),
      currency: "TRY",
      // Snapshot of the counterparty at transfer time: if they later change their name,
      // this movement must still show what the bank sent.
      counterparty: { name: "Mehmet Demir", iban: "TR560001000000000012345678" }
      });
      db.movements.createIndex({ accountId: 1, bookedAt: -1 }); // the statement query

      src/main/java/com/bank/account/ MovementBooking.java Hareket yazılır, bakiye artar ve son 10 hareket listesi $slice ile kısa kalır.

      src/main/java/com/bank/account/MovementBooking.java
      @Service
      class MovementBooking {
      private final MongoTemplate mongo;
      MovementBooking(MongoTemplate mongo) {
      this.mongo = mongo;
      }
      // Two documents change together, so a transaction is justified here
      // (needs a MongoTransactionManager and a replica set).
      @Transactional
      public void book(Movement m) {
      mongo.insert(m);
      Update update = new Update()
      .inc("balance", new Decimal128(m.amount())) // BigDecimal stored as Decimal128
      .push("recentMovements").slice(-10).each(RecentMovement.of(m));
      mongo.updateFirst(query(where("_id").is(m.accountId())), update, Account.class);
      }
      }

      counter-example/ all-movements-embedded.js Şöyle de yazılabilirdi, ama bak ne oluyor: bütün hareketler hesabın içinde ve dizi hiç durmadan büyüyor.

      counter-example/all-movements-embedded.js
      // Every movement pushed into the account itself. Nothing breaks on day one.
      db.accounts.updateOne(
      { _id: "TR330006100519786457841326" },
      { $push: { movements: { amount: NumberDecimal("-1500.00"), description: "Kira" } } }
      );
      // Salary, card spends, bills: the array never stops growing. Every balance check
      // loads the whole history, and a busy account heads for the 16 MB document limit.
      Kafam karıştı, daha basit anlat

      Ortası da olur: son on yorumu zarfa koy, hepsini ayrı bir kutuda tut. Böylece sık okunan kısım hızlı gelir, zarf da şişmez.

      Hızlı kontrolOrta

      Sipariş listesinde müşteri adı gösteriliyor ve her liste için müşteri koleksiyonuna ayrıca gitmek pahalı. Hangisi en dengeli çözüm?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Yazı sayfası son 10 yorumu gösteriyor, yorumlar ise sınırsız. Subset pattern nasıl uygulanır?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar

      Her yerde $lookup. Her okuma koleksiyonları birleştiriyorsa, ilişkisel bir modeli doküman veritabanında yeniden kurmuşsundur.

      Transaction’la modeli kurtarmak. Çok dokümanlı transaction mevcut; ama her iş için gerekiyorsa sınırlar yanlış çizilmiştir.

      Sınırsız id dizisi. Takipçi id’lerini kullanıcı dokümanında tutmak, gömülü yorumlarla aynı sorunu yaşar.

      Şema sürümü yok. Eski ve yeni biçimdeki dokümanlar yan yana yaşar; bir schemaVersion alanı, okuyan kodun hangisiyle karşılaştığını bilmesini sağlar.

      Hızlı kontrolOrta

      Ünlü kullanıcıların takip isteği yavaşlıyor ve bazıları hata veriyor. Hatalı satır hangisi?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Hatalı satıra dokun, sonra kontrol et.

      follow.ts
      TypeScriptUTF-8LF

      Kendini sına

      Şimşek turu1/5

      Doküman modelinde veri, uygulamanın onu nasıl okuduğuna göre şekillenir.

      Soru 1/2İleri

      Bir MongoDB uygulamasında neredeyse her sorgu `$lookup` ile üç dört koleksiyonu birleştiriyor ve çoğu yazma çok dokümanlı transaction kullanıyor. Bu ne söylüyor?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Birlikte okunan ve birlikte değişen veri tek dokümanda durur: tek okuma, tek seferde yazma.
      2. 2 Sınırsız büyüyen listeler gömülmez. MongoDB'de bir dokümanın üst sınırı 16 MB'tır ve büyük doküman her güncellemede pahalıdır.
      3. 3 Kopyalamak bazen hatadır (e-posta), bazen zorunluluktur (faturadaki o günkü adres). Soru şu: bu veri o anın kaydı mı, yoksa hep güncel mi olmalı?
      Sonraki kapı Profilini güncelledin, sayfayı yeniledin ve eski hâli karşına çıktı. Hangi kopyaya baktın? Replikasyon ve Okuma Replikaları — Yazdım Ama Göremiyorum · 9 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı