Data yang sudah saling terhubung lewat referensi menimbulkan pertanyaan baru: apa yang terjadi bila perubahan harus menyentuh banyak field atau bahkan banyak document sekaligus, dan ada pembaca lain yang datang bersamaan? MongoDB menjawab dengan garis batas yang tegas: satu operasi tulis pada satu document selalu atomic — pembaca tidak akan pernah melihat setengah perubahan. Chapter ini memetakan cakupan jaminan itu, membuktikannya lewat kegagalan yang sengaja dipicu, lalu menandai batasnya: lintas document, jaminan itu tidak berlaku.

Satu Operasi, Satu Document, Satu Kesatuan #

Satu panggilan updateOne() bisa mengubah banyak hal sekaligus pada satu document: beberapa field, nested document, bahkan elemen array. Server mengeksekusi seluruh perubahan itu sebagai satu kesatuan — di level storage engine, pembaca lain mendapat versi document sebelum atau sesudah update, tidak pernah di antaranya.

Kita buat satu pesanan dan perbarui tiga aspeknya dalam satu perintah: status, field nested pengiriman, dan array riwayat:

js
use latihan_relasi
db.pesanan.drop()
db.pesanan.insertOne({
  noPesanan: "PSN-001",
  status: "diproses",
  total: 196000,
  pengiriman: { kurir: "Kilat", biaya: 15000 },
  riwayat: [{ kejadian: "pesanan dibuat" }]
})
db.pesanan.updateOne(
  { noPesanan: "PSN-001" },
  {
    $set: { status: "dikirim", "pengiriman.resi": "KI-9911" },
    $inc: { total: 15000 },
    $push: { riwayat: { kejadian: "diserahkan ke kurir" } }
  }
)
db.pesanan.findOne({ noPesanan: "PSN-001" })

Output yang Dihasilkan:

text
switched to db latihan_relasi
true
{
  acknowledged: true,
  insertedId: ObjectId('6ad39aee340ddbcfc825f973')
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  _id: ObjectId('6ad39aee340ddbcfc825f973'),
  noPesanan: 'PSN-001',
  status: 'dikirim',
  total: 211000,
  pengiriman: { kurir: 'Kilat', biaya: 15000, resi: 'KI-9911' },
  riwayat: [ { kejadian: 'pesanan dibuat' }, { kejadian: 'diserahkan ke kurir' } ]
}

Bedah operasinya:

  1. $set: { status: "dikirim", "pengiriman.resi": "KI-9911" }: dua field berubah — satu field puncak, satu field di dalam nested document pengiriman.
  2. $inc: { total: 15000 }: biaya kurir ditambahkan ke total; 196000 menjadi 211000.
  3. $push: { riwayat: ... }: array bertambah satu elemen.

Tiga operator berbeda, tiga bagian document berbeda — tetapi karena semuanya berada dalam satu panggilan updateOne(), hasilnya terlihat serentak. Pembaca yang datang tepat di sela proses tetap melihat document versi lama yang utuh, bukan status baru dengan total lama.

Bukti dari Kegagalan: yang Tak Terganti Tak Terubah #

Jaminan atomic paling mudah dipercaya ketika operasinya gagal. Kita pasang unique index pada sku lalu mencoba mengganti judul dan sku sebuah buku dalam satu panggilan — dengan sku baru yang sudah dipakai buku lain:

js
db.buku.drop()
db.buku.createIndex({ sku: 1 }, { unique: true })
db.buku.insertMany([
  { sku: "BK-001", judul: "Laut Bercerita", harga: 98000 },
  { sku: "BK-002", judul: "Bumi", harga: 89000 }
])
db.buku.updateOne(
  { sku: "BK-002" },
  { $set: { judul: "Bumi (Edisi Revisi)", sku: "BK-001" } }
)

Output yang Dihasilkan:

text
true
sku_1
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39aee340ddbcfc825f974'),
    '1': ObjectId('6ad39aee340ddbcfc825f975')
  }
}
Uncaught 
MongoServerError: Plan executor error during update :: caused by :: E11000 duplicate key error collection: latihan_relasi.buku index: sku_1 dup key: { sku: "BK-001" }

Pembaruan ditolak dengan E11000 duplicate key error — dan itu justru intinya. Karena kedua perubahan berada dalam satu operasi atomic, keduanya sama-sama batal: document Bumi tidak berubah sama sekali, judulnya pun masih yang lama. Tidak ada kondisi setengah terganti.

Sekarang bandingkan dengan dua panggilan terpisah yang melakukan hal yang sama:

js
db.buku.updateOne({ sku: "BK-002" }, { $set: { judul: "Bumi (Edisi Revisi)" } })
db.buku.updateOne({ sku: "BK-002" }, { $set: { sku: "BK-001" } })
db.buku.find({}, { _id: 0 })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
Uncaught 
MongoServerError: Plan executor error during update :: caused by :: E11000 duplicate key error collection: latihan_relasi.buku index: sku_1 dup key: { sku: "BK-001" }
[
  { sku: 'BK-001', judul: 'Laut Bercerita', harga: 98000 },
  { sku: 'BK-002', judul: 'Bumi (Edisi Revisi)', harga: 89000 }
]

Panggilan pertama berhasil mengganti judul; panggilan kedua gagal karena sku bentrok. Hasil akhirnya: judul baru, sku lama — document tertinggal di kondisi setengah jadi. Inilah perbedaan mendasarnya: jaminan atomic berlaku per operasi, dan dua operasi adalah dua kesatuan terpisah. Bila di antara keduanya ada pembaca, pembaca itu bisa melihat kondisi antara yang tidak konsisten.

Batas Jaminan #

Peta lengkap cakupan jaminan atomic pada operasi tulis:

OperasiAtomic?Penjelasan
Satu insertOne, updateOne, replaceOne, deleteOne pada satu documentYaSeluruh perubahan field/nested/array terjadi serentak
Satu updateOne dengan banyak operator ($set, $inc, $push, …)YaSatu operasi = satu kesatuan, seberapa banyak field pun
updateMany / deleteMany pada banyak documentPer documentTiap document atomic, tetapi keseluruhan tidak — bisa berhenti di tengah
insertManyPer documentSisipan sebelum kegagalan tetap tersimpan (perilaku ordered default)
Dua perintah terpisah pada document samaTidakTerlihat di atas: kondisi antara bisa terbaca

Dua implikasi praktis mengikuti tabel ini. Pertama, inilah alasan kuat desain embedded: seluruh data yang harus berubah bersamaan — pesanan beserta isinya, profil beserta alamatnya — sebaiknya satu document, karena atomicity-nya gratis. Kedua, begitu invariant sungguhan melintasi document (saldo dua rekening, stok buku versus pesanan), operasi tunggal tidak lagi cukup; kita perlu pola penjaga di Chapter Write Atomicity dan Optimistic Concurrency, atau transaksi penuh di bagian akhir Section ini.

Rangkuman #

  • Satu operasi tulis pada satu document selalu atomic — berapa pun field, nested document, dan array yang ikut berubah.
  • Pembaca melihat versi sebelum atau sesudah operasi, tidak pernah kondisi antara.
  • Kegagalan membuktikan atomicity: update yang ditolak unique index tidak meninggalkan perubahan apa pun.
  • Dua perintah terpisah tidak dijamin atomic — document bisa tertinggal setengah berubah; insertMany/updateMany hanya atomic per document.

Jaminan per document menutup satu operasi. Masalah berikutnya: bagaimana bila perhitungan sendiri dilakukan di sisi aplikasi sebelum ditulis kembali — di situlah lost update lahir, materi Chapter Write Atomicity.