Query yang menyentuh data di RAM terasa instan; query yang sama terasa berat ketika datanya harus diambil dari disk. Selisih itu yang membuat konsep working set penting: sekumpulan data dan index yang benar-benar rutin diakses oleh beban kerja normal kita. Selama working set muat di memori, performa stabil; begitu ia melampaui cache, latensi merambat ke seluruh aplikasi. Chapter ini mengukur cache WiredTiger di server lab, membaca metrik eviksi dan page fault, lalu menutup dengan indikator dan mitigasinya.

Peta materi Chapter ini:

  • Definisi working set dan bedanya dengan total ukuran data.
  • Cache WiredTiger: ukuran default, konfigurasi server lab, sifat datanya.
  • Eviksi halaman, cache miss, dan page fault level sistem operasi.
  • Indikator working set melebihi cache beserta mitigasinya.

Apa Itu Working Set #

Working set adalah subset data — bukan seluruhnya. Toko buku kita menyimpan jutaan transaksi historis, tetapi yang diakses pengguna dan dashboard setiap hari hanya transaksi minggu berjalan, master data buku, dan index pendukung query aktif. Tumpukan transaksi lama nyaris tidak pernah disentuh — bukan bagian working set.

Ukuran working set menentukan kebutuhan memori yang sesungguhnya, dan bentuknya berubah mengikuti pola akses: kampanye diskon menjelang lebaran menaikkan porsi transaksi panas, laporan tahunan sekali setahun tiba-tiba menarik data dingin ke memori. Pertanyaan yang tepat bukan “berapa RAM untuk seluruh data”, melainkan “berapa RAM untuk data yang rutin dipakai”.

Cache WiredTiger #

WiredTiger, storage engine bawaan MongoDB, menjaga salinan data yang sedang aktif di internal cache miliknya. Ukuran defaultnya adalah nilai terbesar antara 50% × (RAM − 1 GB) atau 256 MB — pada server 16 GB, cache mendapat sekitar 7,5 GB. Server lab kita sengaja dikunci kecil lewat storage.wiredTiger.engineConfig.cacheSizeGB: 0.25 di berkas konfigurasi agar latihan tidak menyerap seluruh RAM sandbox; nilai produksi yang mengikuti rumus di atas jauh lebih besar, jadi angka-angka chapter ini harus dibaca sebagai perilaku, bukan skala.

Dua sifat cache penting dipahami. Pertama, data di dalam cache WiredTiger tidak terkompresi — berbeda dari format di disk yang memakai block compression snappy untuk collection dan prefix compression untuk index, sehingga 1 GB data di disk bisa menempati lebih dari 1 GB di cache. Kedua, cache dibagikan untuk seluruh instance mongod, tidak dipecah per database atau per collection. Mari kita lihat angkanya langsung:

js
use latihan_perf
db.penjualan.drop()
const hasil = db.penjualan.insertMany([
  { _id: 1, waktu: ISODate("2026-03-02T09:15:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, harga_total: 98000, kota: "Jakarta", channel: "toko" },
  { _id: 2, waktu: ISODate("2026-03-02T11:40:00Z"), judul_buku: "Bumi", jumlah: 2, harga_total: 178000, kota: "Bandung", channel: "online" },
  { _id: 3, waktu: ISODate("2026-03-03T10:05:00Z"), judul_buku: "Filosofi Teras", jumlah: 1, harga_total: 89000, kota: "Jakarta", channel: "online" },
  { _id: 4, waktu: ISODate("2026-03-03T14:30:00Z"), judul_buku: "Atomic Habits", jumlah: 3, harga_total: 345000, kota: "Surabaya", channel: "toko" },
  { _id: 5, waktu: ISODate("2026-03-04T08:20:00Z"), judul_buku: "Rumah Kaca", jumlah: 1, harga_total: 72000, kota: "Bandung", channel: "toko" },
  { _id: 6, waktu: ISODate("2026-03-04T13:55:00Z"), judul_buku: "Bumi", jumlah: 1, harga_total: 89000, kota: "Jakarta", channel: "toko" },
  { _id: 7, waktu: ISODate("2026-03-05T09:45:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 2, harga_total: 130000, kota: "Yogyakarta", channel: "online" },
  { _id: 8, waktu: ISODate("2026-03-05T16:10:00Z"), judul_buku: "Laut Bercerita", jumlah: 1, harga_total: 98000, kota: "Surabaya", channel: "online" },
  { _id: 9, waktu: ISODate("2026-03-06T10:30:00Z"), judul_buku: "Atomic Habits", jumlah: 1, harga_total: 115000, kota: "Jakarta", channel: "online" },
  { _id: 10, waktu: ISODate("2026-03-06T15:25:00Z"), judul_buku: "Bumi", jumlah: 4, harga_total: 356000, kota: "Yogyakarta", channel: "toko" },
  { _id: 11, waktu: ISODate("2026-03-07T09:00:00Z"), judul_buku: "Filosofi Teras", jumlah: 2, harga_total: 178000, kota: "Bandung", channel: "online" },
  { _id: 12, waktu: ISODate("2026-03-07T17:35:00Z"), judul_buku: "Hujan Bulan Juni", jumlah: 1, harga_total: 65000, kota: "Jakarta", channel: "toko" }
])
print("tersimpan:", db.penjualan.countDocuments({}))

const cache = db.serverStatus().wiredTiger.cache
print("maksimum cache :", cache["maximum bytes configured"], "byte")
print("terpakai        :", cache["bytes currently in the cache"], "byte")
print("dirty           :", cache["tracked dirty bytes in the cache"], "byte")
print("porsi terpakai  :", (Number(cache["bytes currently in the cache"]) / Number(cache["maximum bytes configured"]) * 100).toFixed(1) + "%")

Output yang Dihasilkan:

text
switched to db latihan_perf
true

tersimpan: 12

maksimum cache : 268435456 byte

terpakai        : 28965119 byte

dirty           : 5276423 byte

porsi terpakai  : 10.8%

Angka maximum bytes configured sebesar 268.435.456 byte adalah 0,25 GB persis seperti konfigurasi. bytes currently in the cache mengukur seluruh halaman yang sedang dipegang — collection, index, dan metadata internal seluruh database di instance ini — bukan hanya latihan_perf. tracked dirty bytes adalah porsi yang sudah berubah dan wajib ditulis ke disk pada checkpoint berikutnya sebelum boleh dibuang.

Eviksi Halaman dan Page Fault #

Ketika cache menipis, WiredTiger mengeluarkan halaman dari memori — eviksi. Halaman bersih dibuang begitu saja; halaman dirty harus ditulis dulu ke disk. Sebaliknya, query yang menyentuh halaman yang tidak ada di cache harus membacanya dari disk — cache miss yang mahal. Kita bisa menyaksikan keduanya lewat pengukuran sebelum-sesudah sebuah query:

js
const c1 = db.serverStatus().wiredTiger.cache
const p1 = db.serverStatus().extra_info
print("sebelum: diminta =", c1["pages requested from the cache"], "| dibaca dari disk =", c1["pages read into cache"])
db.penjualan.find({ channel: "online" }).sort({ harga_total: -1 }).itcount()
const c2 = db.serverStatus().wiredTiger.cache
const p2 = db.serverStatus().extra_info
print("sesudah: diminta =", c2["pages requested from the cache"], "| dibaca dari disk =", c2["pages read into cache"])
const diminta = Number(c2["pages requested from the cache"]) - Number(c1["pages requested from the cache"])
const dariDisk = Number(c2["pages read into cache"]) - Number(c1["pages read into cache"])
print("delta  :", diminta, "halaman diminta,", dariDisk, "dibaca dari disk")
print("eviksi  :", c2["unmodified pages evicted"], "clean +", c2["modified pages evicted"], "dirty (akumulasi)")
print("page faults OS:", Number(p2.page_faults))

Output yang Dihasilkan:

text
sebelum: diminta = 833317 | dibaca dari disk = 722

6

sesudah: diminta = 833342 | dibaca dari disk = 722

delta  : 25 halaman diminta, 0 dibaca dari disk

eviksi  : 330 clean + 770 dirty (akumulasi)

page faults OS: 1

Query menyentuh 25 halaman dan semuanya dilayani dari cache — angka 6 adalah hasil itcount(), enam transaksi channel online. dibaca dari disk tidak bertambah karena data barusan kita insert masih hangat di memori. Eviksi dan page fault bersifat akumulatif sejak server menyala — yang diperhatikan adalah kecepatan naiknya saat beban berjalan, bukan nilai mutlaknya. Rasio pages read into cache / pages requested from the cache sepanjang umur server adalah perkiraan kasar porsi permintaan yang harus turun ke disk — lawan dari cache hit rate.

Ketika Working Set Melebihi Cache #

Data yang tidak muat tidak membuat query gagal — ia hanya membuat hidup server sulit. Sinyalnya terbaca dari beberapa metrik sekaligus:

IndikatorCara membaca
Porsi cache terpakai menahun di atas ~95%Cache penuh bukan sekadar momen sibuk
Eviksi (clean + dirty) naik terus saat beban normalServer sibuk memutar halaman keluar-masuk memori
pages read into cache meroket pada query berulangData yang sama dibaca berulang dari disk — working set tidak tertampung
extra_info.page_faults menanjak saat scanSistem operasi ikut kesulitan menyediakan halaman memori
Latensi baca naik walau opcounters stabilKemacetan pindah ke I/O disk

Mitigasinya menyasar dua arah: perkecil working set atau perbesar memorinya. Index yang tepat membuat query menyentuh jauh lebih sedikit halaman. Pola subset dari Chapter Subset Pattern memindahkan detail yang jarang dibaca keluar dari document yang rutin diakses, sehingga halaman panas lebih ramping. Menaikkan RAM — atau cacheSizeGB pada container yang dibatasi — adalah pilihan terakhir; kompresi tidak menolong karena data di cache memang tidak terkompresi.

Rangkuman #

  • Working set adalah data dan index yang rutin diakses beban kerja normal — bukan total data; ukurannya berubah mengikuti pola akses.
  • Cache WiredTiger default sebesar maksimum antara 50% × (RAM − 1 GB) atau 256 MB; server lab dikunci 0,25 GB lewat konfigurasi, jauh di bawah nilai produksi.
  • Data di cache tidak terkompresi dan dibagikan seluruh instance; eviksi membuang halaman penuh (dirty harus ditulis dulu), dan cache miss berarti baca dari disk.
  • Indikator khas: porsi cache menahun penuh, eviksi dan pages read into cache yang terus menanjak, page fault OS naik. Mitigasi: index tepat, pola subset, lalu RAM.