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:

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, 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:

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 } }
{ _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:

  1. penulisId untuk kebenaran — field ini menghubungkan kembali ke sumber; operasi yang butuh data penulis lengkap tetap berjalan lewat referensi.
  2. penulisNama untuk kecepatan — field ini murni salinan tampilan; dibaca jutaan kali, ditulis hanya saat penulis berganti nama.
  3. Yang tidak ikut disalin — kota penulis 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:

js
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:

text
{
  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:

js
db.buku.updateMany({ penulisId: 1 }, { $set: { penulisNama: "Tere" } })
db.buku.find({ penulisId: 1 }, { judul: 1, penulisNama: 1 })

Output yang Dihasilkan:

text
{
  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:

js
// 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 dataLayak duplikasiSebaiknya referensi saja
Frekuensi perubahanJarang (nama, kategori)Sering (stok, saldo)
Rasio baca : tulisSangat read-heavyWrite-heavy
Ukuran nilaiKecil (satu nama)Besar (deskripsi panjang)
Jumlah salinanBelasanRibuan
Toleransi basangRingan (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 (updateMany dua 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.