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:

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

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

  1. ts: timestamp entri — kombinasi detik Unix dan penghitung urut (i: 6 berarti entri keenam pada detik itu). Secondary melapor posisinya dengan nilai ini; inilah “jam” replikasi.
  2. op: 'd': jenis operasi — d untuk delete, i untuk insert, u untuk update, n untuk no-op internal, c untuk perintah DDL seperti createIndexes.
  3. ns: namespace database.collection tempat perubahan terjadi — latihan_replika.pesanan.
  4. o: operasi itu sendiri. Untuk delete, cukup _id document yang dihapus.
  5. wall: waktu dinding saat entri dibuat — membantu audit oleh manusia.
  6. t dan v: term (periode primary yang menulis) dan versi format oplog — keduanya infrastruktur internal protokol replikasi.
  7. 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:

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

text
[
  {
    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'): field o memuat document lengkap hasil akhirnya — secondary cukup menyisipkan apa adanya.
  • Update (op: 'u'): field o2 menunjuk document mana (berdasar _id), sedangkan o menyimpan diff — perubahan saja. $set { jumlah: 3 } tercatat sebagai diff: { u: { jumlah: 3 } }: sub-field u berarti “update field ini menjadi nilai berikut”. Format $v: 2 ini 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 bila o menyimpan 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:

js
rs.printReplicationInfo()

Output yang Dihasilkan:

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

js
const s = db.getSiblingDB("local").oplog.rs.stats()
({ capped: s.capped, jumlahEntri: s.count, ukuranData: s.size, maxSize: s.maxSize })

Output yang Dihasilkan:

text
{
  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 kunci ts, op, ns, o, dan wall.
  • 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.