Setiap keputusan pemodelan data di MongoDB pada akhirnya bermuara ke satu pertanyaan: data terkait disematkan di dalam document induk, atau disimpan terpisah dan dirujuk lewat _id? Embed memberi baca cepat; reference memberi pemeliharaan tunggal. Keduanya sah, dan pilihan yang tepat bergantung pada pola akses yang sudah kita petakan di Chapter sebelumnya. Chapter ini membandingkan keduanya berdampingan dengan data yang sama, lalu menyarikan kriteria kapan memakai yang mana.

Dua Strategi Dasar #

Embedding menyimpan data terkait sebagai subdocument atau array di dalam document induk — ulasan hidup di dalam buku. Referencing menyimpan data terkait di collection sendiri dan menautkannya lewat field yang memuat _id — buku hanya menyimpan penulisId, dan detail penulis tinggal di collection penulis.

Pada diagram kiri, ulasan adalah bagian dari document buku — membaca buku sekaligus membawa ulasannya. Pada diagram kanan, document buku hanya menunjuk ke document penulis; detail penulis diambil terpisah saat dibutuhkan.

Perbandingan Berdampingan #

AspekEmbedReference
Kecepatan baca data terkaitSatu query, tanpa joinDua query atau $lookup
Update nilai bersamaHarus menyentuh semua induk yang menyalinSatu tempat, selesai
Atomicity tulis induk + terkaitSatu document, atomic secara bawaanButuh transaksi multi-document
Pertumbuhan dataArray membesar, arah limit 16 MBAnak tumbuh bebas di collection sendiri
Ukuran document indukMembengkak mengikuti anakTetap ramping
Konsistensi antar salinanBerisiko basang (stale)Satu sumber kebenaran
Akses data terkait tanpa indukHarus “membedah” indukQuery langsung pada collection anak

Tidak ada kolom pemenang — tiap baris adalah tarik-menarik antara kecepatan baca dan kemudahan tulis.

Kapan Embed, Kapan Refer #

Gabungkan tiga pertanyaan dari tabel tersebut, dan jawabannya terlihat:

Embed saat data terkait dimiliki induk (ulasan tidak berarti tanpa bukunya), jarang berubah nilainya, dan jumlahnya terbatas (puluhan, bukan ribuan). Refer saat data besar, dipakai bersama oleh banyak induk (satu penulis menaungi banyak buku), berubah sering, atau tak terbatas jumlahnya.

Kita uji rasanya di mongosh dengan toko buku — ulasan untuk kasus embed, penulis untuk kasus refer:

js
use latihan_modeling
db.dropDatabase()
db.penulis.insertMany([
  { _id: 1, nama: "Tere Liye", kota: "Jakarta" },
  { _id: 2, nama: "Leila S. Chudori", kota: "Jakarta" }
])
db.buku.insertMany([
  {
    _id: 101, judul: "Bumi", harga: 89000, penulisId: 1,
    ulasan: [
      { pembaca: "Budi", bintang: 4 },
      { pembaca: "Siti", bintang: 5 }
    ]
  },
  {
    _id: 102, judul: "Laut Bercerita", harga: 98000, penulisId: 2,
    ulasan: [ { pembaca: "Dewi", bintang: 5 } ]
  }
])

Output yang Dihasilkan:

text
switched to db latihan_modeling
{ ok: 1, dropped: 'latihan_modeling' }
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
{ acknowledged: true, insertedIds: { '0': 101, '1': 102 } }

Dua jalur baca untuk halaman detail buku — jalur embed membaca ulasan sekalian, jalur refer menggabungkan penulis lewat $lookup:

js
db.buku.findOne({ _id: 101 }, { judul: 1, ulasan: 1 })
db.buku.aggregate([
  { $match: { _id: 101 } },
  { $lookup: { from: "penulis", localField: "penulisId", foreignField: "_id", as: "penulis" } },
  { $project: { judul: 1, penulis: { nama: 1, kota: 1 } } }
])

Output yang Dihasilkan:

text
{
  _id: 101,
  judul: 'Bumi',
  ulasan: [ { pembaca: 'Budi', bintang: 4 }, { pembaca: 'Siti', bintang: 5 } ]
}
[
  {
    _id: 101,
    judul: 'Bumi',
    penulis: [ { nama: 'Tere Liye', kota: 'Jakarta' } ]
  }
]

Bedah kedua jalur:

  1. findOne dengan projection — field ulasan sudah berada di dalam document, jadi satu query membawa semua yang dibutuhkan; inilah keunggulan embed pada jalur baca.
  2. $lookup dengan localField/foreignField — menggabungkan penulisId di sisi buku dengan _id di sisi penulis, hasilnya array penulis; operasi ini sah, tetapi dikerjakan saat query berjalan.
  3. $project menyusutkan hasil — field kota pada penulis ikut diambil karena dibutuhkan halaman; dengan refer, kita bebas memilih field mana yang digabungkan.

Harga yang Dibayar Saat Menulis #

Sekarang balik arah: penulis dengan _id: 1 pindah kota. Model refer menuntaskan dengan satu update; model embed — seandainya nama penulis tersalin ke setiap buku — harus menyentuh semuanya:

js
db.penulis.updateOne({ _id: 1 }, { $set: { kota: "Bandung" } })
db.penulis.find({ _id: 1 })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
[ { _id: 1, nama: 'Tere Liye', kota: 'Bandung' } ]

Satu updateOne, satu tempat, semua pembaca penulis melihat nilai baru — inilah yang tidak dimiliki model embed terhadap data yang diduplikasi. Sebaliknya, menambah ulasan baru pada model embed hanyalah satu $push ke document buku tanpa menyentuh collection lain; pada model refer murni, operasi itu menjadi insert ke collection terpisah ditambah context bukuId di setiap baris.

Rangkuman #

  • Embed mengutamakan baca: satu query membawa induk beserta data terkait, dan tulis pada satu document bersifat atomic.
  • Refer mengutamakan pemeliharaan: data bersama berubah di satu tempat, anak tumbuh tanpa membebani induk.
  • Embed untuk data milik induk yang jarang berubah dan jumlahnya terbatas; refer untuk data besar, berubah sering, dipakai bersama, atau tak terbatas.
  • $lookup menutup kebutuhan join saat query berjalan, tetapi jalur panas sebaiknya tetap dirancang tanpanya.

Dua chapter berikutnya menurunkan kerangka ini ke bentuk relasi yang paling sering dijumpai: one-to-one dan one-to-many.