Secondary tahu persis apa yang harus disalin karena primary mencatat setiap perubahan ke satu tempat: operation log, atau oplog. Oplog adalah mata rantai antara primary dan secondary sekaligus asal-usul sebagian besar perilaku replikasi yang kita pelajari di section ini — dari replication lag hingga keharusan initial sync. Chapter ini membuka isi oplog rs0 secara langsung, membedah format entrinya, lalu mengukur jendela waktunya dengan rs.printReplicationInfo().
local.oplog.rs: Capped Collection Perubahan #
Oplog tersimpan di database local — database sistem yang tidak ikut direplikasi — sebagai collection oplog.rs. Collection ini bersifat capped: ukurannya dibatasi, dan begitu penuh, entri tertua tertimpa entri baru layaknya pita rekam yang berputar. Setiap operasi tulis menghasilkan tepat satu entri.
Kita panggang tiga jenis operasi — insert, update, delete — lalu intip entri terakhir:
use latihan_replika
db.dropDatabase()
db.pesanan.insertOne({ kode: "PSN-001", pelanggan: "Budi", buku: "Laut Bercerita", jumlah: 2 })
db.pesanan.updateOne({ kode: "PSN-001" }, { $set: { jumlah: 3 } })
db.pesanan.deleteOne({ kode: "PSN-001" })
db.getSiblingDB("local").oplog.rs.find().sort({ $natural: -1 }).limit(1)Output yang Dihasilkan:
switched to db latihan_replika
{ ok: 1, dropped: 'latihan_replika' }
{ acknowledged: true, insertedId: ObjectId('6ad3d4823d6cfaac0433ef6c') }
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
{ acknowledged: true, deletedCount: 1 }
[
{
lsid: {
id: UUID('24326b94-735e-484e-9ab2-587272986610'),
uid: Binary.createFromBase64('47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', 0)
},
txnNumber: Long('3'),
op: 'd',
ns: 'latihan_replika.pesanan',
ui: UUID('57b60d48-651e-4161-a233-0908dcf4adac'),
o: { _id: ObjectId('6ad3d4823d6cfaac0433ef6c') },
stmtId: 0,
ts: Timestamp({ t: 1792267394, i: 6 }),
t: Long('8'),
v: Long('2'),
wall: ISODate('2026-10-17T20:03:14.749Z'),
prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long('-1') }
}
]Entri ini adalah jejak dari deleteOne kita. Mari kita bedah field-field kuncinya:
ts: timestamp entri — kombinasi detik Unix dan penghitung urut (i: 6berarti entri keenam pada detik itu). Secondary melapor posisinya dengan nilai ini; inilah “jam” replikasi.op: 'd': jenis operasi —duntuk delete,iuntuk insert,uuntuk update,nuntuk no-op internal,cuntuk perintah DDL seperticreateIndexes.ns: namespacedatabase.collectiontempat perubahan terjadi —latihan_replika.pesanan.o: operasi itu sendiri. Untuk delete, cukup_iddocument yang dihapus.wall: waktu dinding saat entri dibuat — membantu audit oleh manusia.tdanv: term (periode primary yang menulis) dan versi format oplog — keduanya infrastruktur internal protokol replikasi.lsid/txnNumber: identitas session dan nomor transaksi pemanggil — jejak yang memungkinkan replikasi menjaga urutan per session.
Tiga Operasi, Tiga Bentuk Entri #
Sekarang kita tarik ketiga entri milik collection pesanan sekaligus, dengan projection agar fokus pada field inti:
db.getSiblingDB("local").oplog.rs.find(
{ ns: "latihan_replika.pesanan" },
{ _id: 0, ts: 1, op: 1, ns: 1, o: 1, o2: 1 }
).sort({ $natural: -1 })Output yang Dihasilkan:
[
{
op: 'd',
ns: 'latihan_replika.pesanan',
o: { _id: ObjectId('6ad3d4823d6cfaac0433ef6c') },
ts: Timestamp({ t: 1792267394, i: 6 })
},
{
op: 'u',
ns: 'latihan_replika.pesanan',
o: { '$v': 2, diff: { u: { jumlah: 3 } } },
o2: { _id: ObjectId('6ad3d4823d6cfaac0433ef6c') },
ts: Timestamp({ t: 1792267394, i: 5 })
},
{
op: 'i',
ns: 'latihan_replika.pesanan',
o: {
_id: ObjectId('6ad3d4823d6cfaac0433ef6c'),
kode: 'PSN-001',
pelanggan: 'Budi',
buku: 'Laut Bercerita',
jumlah: 2
},
o2: { _id: ObjectId('6ad3d4823d6cfaac0433ef6c') },
ts: Timestamp({ t: 1792267394, i: 4 })
}
]Ketiga bentuknya berbeda dan setiap perbedaan punya alasan:
- Insert (
op: 'i'): fieldomemuat document lengkap hasil akhirnya — secondary cukup menyisipkan apa adanya. - Update (
op: 'u'): fieldo2menunjuk document mana (berdasar_id), sedangkanomenyimpan diff — perubahan saja.$set { jumlah: 3 }tercatat sebagaidiff: { u: { jumlah: 3 } }: sub-fielduberarti “update field ini menjadi nilai berikut”. Format$v: 2ini menggantikan format operator lama sejak MongoDB 5.0. - Delete (
op: 'd'): hanya_id— secondary menghapus document yang sama berdasar kunci yang sama. Format diff inilah yang membuat oplog idempoten: entri update bukan snapshot document hasil akhir, melainkan instruksi perubahan. Secondary menerapkan diff yang sama di atas salinan yang identik, sehingga hasilnya pasti sama — dan menerapkan ulang entri yang sama tidak mengubah hasil (idempotensi). Bayangkan bilaomenyimpan document pengganti penuh: dua update paralel pada satu document akan saling menimpa, dan secondary tidak bisa menceritakan apa yang sebenarnya terjadi.
Ukuran Oplog dan Jendela Waktu #
Karena oplog adalah capped collection, pertanyaan operasional utamanya adalah: berapa lama riwayat perubahan tersimpan sebelum terdorong keluar? rs.printReplicationInfo() menjawabnya:
rs.printReplicationInfo()Output yang Dihasilkan:
actual oplog size
'990 MB'
---
configured oplog size
'990 MB'
---
log length start to end
'26142 secs (7.26 hrs)'
---
oplog first event time
'Sat Oct 17 2026 12:47:32 GMT+0000 (Coordinated Universal Time)'
---
oplog last event time
'Sat Oct 17 2026 20:03:14 GMT+0000 (Coordinated Universal Time)'
---
now
'Sat Oct 17 2026 20:03:14 GMT+0000 (Coordinated Universal Time)'Baris log length start to end adalah jendela oplog: rentang waktu dari entri tertua hingga terbaru — pada keluaran di atas sekitar 7 jam. Angka ini bergantung pada dua hal: ukuran oplog dan laju tulis cluster. Kita verifikasi ukurannya langsung dari collection:
const s = db.getSiblingDB("local").oplog.rs.stats()
({ capped: s.capped, jumlahEntri: s.count, ukuranData: s.size, maxSize: s.maxSize })Output yang Dihasilkan:
{
capped: true,
jumlahEntri: 13615,
ukuranData: 131689785,
maxSize: 1038090240
}maxSize sekitar 990 MB — sesuai keluaran printReplicationInfo di atas. MongoDB menghitung ukuran default oplog sebagai 5% dari ruang disk kosong, dengan batas bawah 990 MB dan batas atas 50 GB; oplog rs0 jatuh tepat di batas bawah karena disk sandbox jauh lebih besar daripada data latihan kita.
Jendela Habis, Secondary Tertinggal #
Jendela waktu adalah umur keselamatan bagi secondary yang sedang tidak sehat. Bayangkan secondary mati selama 10 jam, sementara jendela oplog hanya 7 jam: saat node itu hidup kembali, entri oplog yang dibutuhkannya untuk mengejar ketertinggalan sudah tertimpa. Secondary tidak bisa “menebak” perubahan yang hilang — satu-satunya jalan adalah initial sync: menyalin seluruh data dari awal, lalu melanjutkan replikasi normal.
Implikasinya sederhana: jendela oplog harus lebih panjang dari dugaan waktu henti terpanjang yang ingin Anda pulihkan tanpa initial sync. Cluster dengan laju tulis tinggi menghabiskan jendela lebih cepat — 990 MB bisa berarti hitungan jam saja — sehingga oplog kerap diperbesar lewat opsi --oplogSize (dalam MB) saat mongod pertama kali dinyalakan, atau diubah dengan replSetResizeOplog pada cluster yang sudah berjalan. Mengubah --oplogSize setelah oplog dibuat tidak berlaku mundur: ukuran ditetapkan saat oplog pertama kali dibentuk.
Rangkuman #
- Oplog adalah capped collection
local.oplog.rs; setiap operasi tulis menghasilkan satu entri dengan field kuncits,op,ns,o, danwall. - Insert tercatat sebagai document penuh, delete sebagai
_id, dan update sebagai diff format$v: 2— bentuk yang ringkas dan idempoten. rs.printReplicationInfo()melaporkan ukuran oplog dan jendela waktunya; default 5% ruang disk dengan batas 990 MB sampai 50 GB.- Secondary yang down lebih lama dari jendela oplog kehilangan jejak perubahan dan harus initial sync ulang.
Dengan oplog di tangan, kita siap menyusun dan mengubah konfigurasi replica set itu sendiri — materi Chapter Konfigurasi Replication.