Duplikasi data adalah keputusan menyimpan satu fakta di lebih dari satu tempat — nama penulis hadir di collection penulis sekaligus tersalin di setiap document buku. Di dunia normalisasi ini penyakit; di document modeling ini alat yang disengaja untuk mempercepat baca. Namun alat ini punya tagihan yang pasti jatuh tempo: begitu fakta berubah, semua salinannya harus menyusul. Chapter ini menguji siklus lengkapnya — duplikasi, data basang, lalu sinkronisasi.
Mengapa Duplikasi Layak Dilakukan #
Kembali ke pola akses: halaman detail buku butuh judul, harga, dan nama penulis dalam satu tampilan. Kalau nama hanya ada di penulis, setiap halaman menuntut query kedua atau $lookup. Menyalin nama penulis ke setiap buku membuat halaman itu cukup satu findOne — dan karena nama penulis jarang berubah, salinannya berumur panjang:
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, penulisNama: "Tere Liye" },
{ _id: 102, judul: "Laut Bercerita", harga: 98000, penulisId: 2, penulisNama: "Leila S. Chudori" }
])
db.buku.findOne({ _id: 101 }, { judul: 1, harga: 1, penulisNama: 1 })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 } }
{ _id: 101, judul: 'Bumi', harga: 89000, penulisNama: 'Tere Liye' }Satu query membawa seluruh kebutuhan halaman. Perhatikan bahwa penulisId tetap disimpan — salinan hanya untuk tampilan, referensi tetap menjadi tali ke sumber kebenaran. Trik ini punya nama resmi di kumpulan pola desain MongoDB — Extended Reference — yang kita ringkas di Chapter Schema Design Patterns.
Bedah komposisi duplikasinya:
penulisIduntuk kebenaran — field ini menghubungkan kembali ke sumber; operasi yang butuh data penulis lengkap tetap berjalan lewat referensi.penulisNamauntuk kecepatan — field ini murni salinan tampilan; dibaca jutaan kali, ditulis hanya saat penulis berganti nama.- Yang tidak ikut disalin —
kotapenulis tidak dibutuhkan halaman detail, jadi tidak diduplikasi; duplikasi selektif menjaga biaya sinkron tetap kecil.
Risiko: Data Basang #
Sekarang penulis dengan _id: 1 resmi mengganti nama terbitan menjadi nama pena baru. Sumber kebenaran diperbarui, salinan belum:
db.penulis.updateOne({ _id: 1 }, { $set: { nama: "Tere" } })
db.penulis.find({ _id: 1 })
db.buku.find({ penulisId: 1 }, { judul: 1, penulisNama: 1 })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
[ { _id: 1, nama: 'Tere', kota: 'Jakarta' } ]
[ { _id: 101, judul: 'Bumi', penulisNama: 'Tere Liye' } ]Dua query menampilkan dua dunia: penulis sudah menyebut Tere, sementara kedua salinan di buku masih memakai nama lama. Selama jeda ini, pembaca halaman buku melihat data basang — inilah biaya sesungguhnya dari duplikasi, dan lama jendela basang ditentukan oleh seberapa cepat strategi sinkronisasi kita bekerja.
Strategi Sinkronisasi #
Cara pertama: pembaruan dua arah eksplisit. Aplikasi yang mengganti nama penulis langsung menyapu semua salinannya:
db.buku.updateMany({ penulisId: 1 }, { $set: { penulisNama: "Tere" } })
db.buku.find({ penulisId: 1 }, { judul: 1, penulisNama: 1 })Output yang Dihasilkan:
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
[ { _id: 101, judul: 'Bumi', penulisNama: 'Tere' } ]updateMany menyentuh semua salinan sekaligus — satu perintah, hasil konsisten. Kelemahannya tetap ada: kedua update (sumber dan salinan) adalah dua operasi terpisah, sehingga pembaca di antara keduanya masih bisa melihat dunia setengah perbarui. Transaksi multi-document mengikat keduanya jadi atom, dan itu dibahas tuntas di Section Transactions.
Cara kedua: change streams. Untuk aplikasi dengan banyak layanan yang menulis, penyapuan manual mudah terlupakan. Change streams memungkinkan satu pemroses mendengarkan setiap perubahan pada penulis dan menyegarkan salinan secara otomatis:
// mdtest: skip
// Ilustrasi: pemroses terpisah mendengarkan perubahan penulis lalu menyapu salinan
db.penulis.watch().on("change", (event) => {
if (event.operationType === "update" && event.updateDescription.updatedFields.nama) {
db.buku.updateMany(
{ penulisId: event.documentKey._id },
{ $set: { penulisNama: event.updateDescription.updatedFields.nama } }
)
}
})Alur kerjanya: setiap update pada penulis menghasilkan change event berisi field apa saja yang berubah; pemroses membaca updatedFields.nama lalu menjalankan updateMany yang sama seperti cara manual — hanya saja tanpa bergantung pada disiplin setiap penulis kode. Membangun pemroses andal dengan resume token adalah bahasan khusus Section Change Streams.
Kapan Boleh Duplikasi #
| Kondisi data | Layak duplikasi | Sebaiknya referensi saja |
|---|---|---|
| Frekuensi perubahan | Jarang (nama, kategori) | Sering (stok, saldo) |
| Rasio baca : tulis | Sangat read-heavy | Write-heavy |
| Ukuran nilai | Kecil (satu nama) | Besar (deskripsi panjang) |
| Jumlah salinan | Belasan | Ribuan |
| Toleransi basang | Ringan (tampilan) | Ketat (tagihan, kuota) |
Rangkuman #
- Duplikasi membeli kecepatan baca: satu query membawa data induk plus salinan data terkait.
- Setiap salinan adalah utang konsistensi — data basang muncul di jeda antara perubahan sumber dan penyegaran salinan.
- Sinkronisasi bisa eksplisit (
updateManydua arah, idealnya dalam satu transaksi) atau otomatis lewat change streams. - Amplifikasi duplikasi: jarang berubah, read-heavy, kecil, dan toleran basang; selain itu simpan sekali dan rujuk.
Duplikasi yang tidak terkendali pada satu field saja sudah cukup berisiko — bab berikutnya membahas pertumbuhan yang lebih fisik: document yang membesar tanpa henti.