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 #
| Aspek | Embed | Reference |
|---|---|---|
| Kecepatan baca data terkait | Satu query, tanpa join | Dua query atau $lookup |
| Update nilai bersama | Harus menyentuh semua induk yang menyalin | Satu tempat, selesai |
| Atomicity tulis induk + terkait | Satu document, atomic secara bawaan | Butuh transaksi multi-document |
| Pertumbuhan data | Array membesar, arah limit 16 MB | Anak tumbuh bebas di collection sendiri |
| Ukuran document induk | Membengkak mengikuti anak | Tetap ramping |
| Konsistensi antar salinan | Berisiko basang (stale) | Satu sumber kebenaran |
| Akses data terkait tanpa induk | Harus “membedah” induk | Query 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:
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:
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:
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:
{
_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:
findOnedengan projection — fieldulasansudah berada di dalam document, jadi satu query membawa semua yang dibutuhkan; inilah keunggulan embed pada jalur baca.$lookupdenganlocalField/foreignField— menggabungkanpenulisIddi sisi buku dengan_iddi sisi penulis, hasilnya arraypenulis; operasi ini sah, tetapi dikerjakan saat query berjalan.$projectmenyusutkan hasil — fieldkotapada 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:
db.penulis.updateOne({ _id: 1 }, { $set: { kota: "Bandung" } })
db.penulis.find({ _id: 1 })Output yang Dihasilkan:
{
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.
$lookupmenutup 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.