Filter saldo: { $gte: nominal } menutup satu syarat, tetapi banyak kasus tidak bisa dinyatakan sebagai kondisi tunggal: menghitung poin plus level, menyunting profil yang mengubah banyak field, atau memproses pesanan yang statusnya saling bergantung. Untuk itu kita perlu tahu apakah document masih sama seperti saat kita membacanya — dan cara MongoDB mengatakannya adalah pola optimistic concurrency: simpan nomor versi, bawa versi itu di filter update, dan ulangi seluruh proses bila nomornya sudah tidak cocok. Kita bangun polanya bertahap di collection akun, lalu membandingkannya dengan pendekatan pesimis yang tidak tersedia di MongoDB.
Pola Version Field #
Mekanismenya sederhana: setiap document membawa field version. Setiap update membaca versi saat ini, mencantumkannya di filter, dan menaikkan versi lewat $inc. Bila di antara waktu baca dan tulis ada proses lain yang lebih dulu menulis, versi di filter tidak cocok lagi — matchedCount menjadi 0 dan update kita ditolak:
use latihan_relasi
db.akun.drop()
db.akun.insertOne({ pengguna: "Siti", saldo: 50000, version: 1 })
var idSiti = db.akun.findOne({ pengguna: "Siti" })._id
// Worker 1 membaca document (version 1) lalu menulis
db.akun.updateOne(
{ _id: idSiti, version: 1 },
{ $set: { saldo: 65000 }, $inc: { version: 1 } }
)
// Worker 2 masih memegang salinan lama (version 1) — filter tidak cocok
db.akun.updateOne(
{ _id: idSiti, version: 1 },
{ $set: { saldo: 70000 }, $inc: { version: 1 } }
)
db.akun.findOne({ _id: idSiti })Output yang Dihasilkan:
switched to db latihan_relasi
true
{
acknowledged: true,
insertedId: ObjectId('6ad39af0fee4281f12a4eb92')
}
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
{
acknowledged: true,
insertedId: null,
matchedCount: 0,
modifiedCount: 0,
upsertedCount: 0
}
{
_id: ObjectId('6ad39af0fee4281f12a4eb92'),
pengguna: 'Siti',
saldo: 65000,
version: 2
}[Function: tarikDenganRetry]
sukses: tarik 20000 pada percobaan 1
sukses: tarik 20000 pada percobaan 1
ditolak: saldo tidak cukup (percobaan 1)Alur fungsinya per baris:
for (var percobaan = 1; ...): batas percobaan mencegah perulangan tanpa akhir pada sistem yang sangat sibuk.db.akun.findOne(...): setiap percobaan selalu membaca versi terbaru — tidak ada data basi yang dibawa antar percobaan.{ _id, version, saldo: { $gte: nominal } }: versi dan syarat saldo diperiksa server-side dalam satu operasi atomic.hasil.matchedCount === 1: satu-satunya penanda keberhasilan — versi cocok dan syarat terpenuhi.- Return string: pada aplikasi nyata ini bisa berupa object hasil,
throw, atau nilai balik driver; string dipakai agar output REPL mudah dibaca.
Tiga pemanggilan menutup skenarionya: dua penarikan @20000 berhasil pada percobaan pertama, dan penarikan 40000 ditolak karena saldo tersisa 25000. Pada sistem nyata dengan banyak worker, attempt yang kalah versi akan masuk ke percobaan kedua atau ketiga — persaingannya sehat karena setiap percobaan membaca data segar.
Optimis atau Pesimis #
Dua filosofi kendali konkurensi berhadapan di sini:
| Aspek | Pessimistic | Optimistic (pola kita) |
|---|---|---|
| Strategi | kunci document lebih dulu, baru mengubah | tulis begitu, deteksi konflik belakangan |
| Penahan | writer lain menunggu kunci lepas | writer kalah mengulang dari baca |
| Cocok untuk | konflik sangat sering, transaksi panjang | konflik jarang, operasi singkat |
| Di MongoDB | tidak tersedia — tidak ada API kunci per document | pola standar, dibangun dari filter + $inc |
MongoDB tidak menyediakan SELECT ... FOR UPDATE ala database relasional; tidak ada cara untuk “memesan” document sebelum mengubahnya. Pola version field mengasumsikan konflik itu kejadian langka (optimis), dan membayar biaya retry hanya ketika asumsi itu salah. Untuk perubahan yang melintasi beberapa document sekaligus, pola ini pun belum cukup — disanalah transaksi multi-document masuk, beberapa chapter lagi.
Rangkuman #
- Version field: filter
{ _id, version: n }+$inc: { version: 1 };matchedCount: 0berarti document berubah di tangan proses lain. - Penolakan bukan galat melainkan sinyal retry: baca ulang versi terbaru, hitung ulang, coba lagi dengan batas percobaan.
- MongoDB tidak punya API lock per document (pesimis); optimistic concurrency adalah pola standar dan murah saat konflik jarang.
- Semua penulis harus mengikuti pola yang sama — satu penulis nakal membuka celah lost update kembali.
Satu document kini aman dari pemburu versi. Invariant berikutnya lebih besar — dua saldo yang harus bergerak bersama — dan itu wilayah transaksi, dimulai dari peta besarnya di Chapter Transactions Overview.