Menjalankan transaksi itu mudah — menjalankannya tanpa merusak kesehatan sistem adalah soal lain. Selama transaksi terbuka, server menahan lock atas document yang disentuh dan menyimpan snapshot di cache WiredTiger; transaksi yang lambat membuat transaksi lain menunggu, konflik bermunculan, dan cache tertekan. Chapter ini merumuskan aturan praktis merancang batas transaksi: sekecil apa, sependek apa, dan bagaimana merancang coba-ulang yang aman — lalu memverifikasi batas waktu bawaan server langsung dari rs0.

Aturan Praktis #

LakukanHindari
Awali transaksi setelah semua bacaan persiapan selesaiMembuka transaksi lalu menunggu input pengguna
Masukkan hanya operasi tulis yang membentuk invariantMenjejalkan seluruh alur bisnis (validasi, kirim email) ke dalam transaksi
Targetkan durasi di bawah satu detik; hitungan milidetik lebih baikTransaksi yang memproses banyak document sekaligus dalam satu paket
Rancang coba-ulang yang idempotenMengulang transaksi yang efeknya bisa berlipat
Tangani errorLabels pada setiap kegagalanMengabaikan galat commit dan berharap semuanya baik-baik saja

Dua aturan pertama adalah satu prinsip yang sama: baca dulu di luar, tulis cepat di dalam. Validasi “saldo cukup tidak”, “buku masih dijual tidak”, semuanya bisa dilakukan sebelum startTransaction() — data bisa berubah sedikit di sela waktu itu, tetapi itulah gunanya filter penjaga (saldo: { $gte: nominal }) di dalam operasi tulis. Transaksi hanya untuk bagian yang tidak bisa ditunda: rangkaian tulis yang harus menyatu.

Batas kerasnya di sisi server: transaksi yang terlalu lama hidup akan dipaksa berhenti. Kita tanyakan langsung ke rs0:

js
db.adminCommand({ getParameter: 1, transactionLifetimeLimitSeconds: 1 })

Output yang Dihasilkan:

text
{
  transactionLifetimeLimitSeconds: 60,
  ok: 1,
  '$clusterTime': {
    clusterTime: Timestamp({ t: 1792252932, i: 11 }),
    signature: {
      hash: Binary.createFromBase64('AAAAAAAAAAAAAAAAAAAAAAAAAAA=', 0),
      keyId: Long('0')
    }
  },
  operationTime: Timestamp({ t: 1792252932, i: 11 })
}
text
switched to db latihan_relasi
true
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad39c06a3619e0395db0ccb'),
    '1': ObjectId('6ad39c06a3619e0395db0ccc')
  }
}
kode_1
text
[Function: transferIdempoten]

[
  { pengguna: 'Budi', saldo: 80000 },
  { pengguna: 'Siti', saldo: 70000 }
]
text
Ditolak: E11000 duplicate key error collection: latihan_relasi.mutasi index: kode_1 dup key: { kode: "TRF-777" }

[
  { pengguna: 'Budi', saldo: 80000 },
  { pengguna: 'Siti', saldo: 70000 }
]
[ { kode: 'TRF-777', dari: 'Budi', ke: 'Siti', nominal: 20000 } ]

Percobaan kedua ditolak E11000 pada kode: "TRF-777"; transaksi batal total, dan saldo tetap 80000/70000 — persis seperti hasil percobaan pertama. Satu mutasi TRF-777 tercatat. Berapa kali pun request diulang, efeknya tidak berlipat: inilah syarat yang membuat strategi coba-ulang transaksi menjadi aman.

Rangkuman #

  • Selama terbuka, transaksi menahan lock dan memakan cache — kuncinya: baca persiapan di luar, tulis sesingkat mungkin di dalam.
  • Batas waktu bawaan server: 60 detik (transactionLifetimeLimitSeconds), diverifikasi langsung dari rs0; satu transaksi menjadi banyak entri oplog yang masing-masing ≤ 16MB.
  • Coba-ulang aman hanya bila idempoten: idempotency key (kode transfer) + unique index membuat percobaan kedua tertolak tanpa efek samping.
  • Penjagaan bisnis tetap relevan di dalam transaksi — filter $gte dan pemeriksaan matchedCount menjaga invariant, throw memicu abort.

Transaksi menuntaskan sisi penulisan yang konsisten. Satu dimensi terakhir menunggu: konsistensi pembacaan di tengah replication — kombinasi read concern, write concern, dan session, pembahasan penutup Section ini.