explain() mode queryPlanner (Chapter sebelumnya) hanya menunjukkan niat: index mana yang akan dipakai. Mode executionStats mengeksekusi query sungguhan sambil mencatat tagihannya — berapa kunci index diperiksa, berapa document dibaca, berapa lama waktunya. Dua query bisa sama-sama mengembalikan lima hasil, tetapi yang satu membaca lima document dan yang lain lima juta; angka-angka inilah yang membedakan perbaikan performa asli dari tebakan. Chapter ini berfokus pada cara membaca dan menilai angka-angka tersebut.
Peta materi Chapter ini:
- Menjalankan
explain("executionStats")pada query tanpa index dan dengan index. - Empat metrik utama:
nReturned,totalKeysExamined,totalDocsExamined,executionTimeMillis. - Rasio efisiensi dan heuristik kondisi sehat.
Menjalankan Query dan Mencatat Tagihannya #
Siapkan kembali collection transaksi toko buku, kali ini tanpa index agar kita bisa membandingkan dua kondisi:
use latihan_perf
db.penjualan.drop()
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" }
])Output yang Dihasilkan:
switched to db latihan_perf
true
{
acknowledged: true,
insertedIds: {
'0': 1,
'1': 2,
'2': 3,
'3': 4,
'4': 5,
'5': 6,
'6': 7,
'7': 8,
'8': 9,
'9': 10,
'10': 11,
'11': 12
}
}Mode executionStats diaktifkan dengan memberi argumen string pada explain(). Jalankan pada query transaksi kota Jakarta — sebelum index dibuat:
db.penjualan.find({ kota: "Jakarta" }).explain("executionStats")Output yang Dihasilkan:
{
explainVersion: '1',
queryPlanner: {
winningPlan: {
isCached: false,
stage: 'COLLSCAN',
filter: { kota: { '$eq': 'Jakarta' } },
direction: 'forward'
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 5,
executionTimeMillis: 0,
totalKeysExamined: 0,
totalDocsExamined: 12,
executionStages: {
stage: 'COLLSCAN',
filter: { kota: { '$eq': 'Jakarta' } },
nReturned: 5,
works: 13,
advanced: 5,
needTime: 7,
isEOF: 1,
docsExamined: 12,
seeks: 0
}
},
command: {
find: 'penjualan',
filter: { kota: 'Jakarta' },
'$db': 'latihan_perf'
},
ok: 1
}Seperti Chapter sebelumnya, output explain() di sini dipadatkan — field teknis (nss, multiKeyPaths, serverInfo, serverParameters, metadata replikasi) dipangkas agar fokus pada angka. Bagian pentingnya berada di field executionStats: pada collection 12 document, server membaca seluruh 12 document (totalDocsExamined) untuk mengembalikan 5 hasil (nReturned) — sisa 7 document dibaca sia-sia karena tidak ada index yang bisa memangkas pencarian. Sekarang buat index dan jalankan query yang persis sama:
db.penjualan.createIndex({ kota: 1 })
db.penjualan.find({ kota: "Jakarta" }).explain("executionStats")Output yang Dihasilkan:
kota_1
{
explainVersion: '1',
queryPlanner: {
winningPlan: {
isCached: false,
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: { kota: 1 },
indexName: 'kota_1',
indexBounds: { kota: [ '["Jakarta", "Jakarta"]' ] }
}
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 5,
executionTimeMillis: 0,
totalKeysExamined: 5,
totalDocsExamined: 5,
executionStages: {
stage: 'FETCH',
nReturned: 5,
works: 6,
advanced: 5,
docsExamined: 5,
inputStage: {
stage: 'IXSCAN',
nReturned: 5,
works: 6,
advanced: 5,
keysExamined: 5,
seeks: 1
}
}
},
command: {
find: 'penjualan',
filter: { kota: 'Jakarta' },
'$db': 'latihan_perf'
},
ok: 1
}Perbandingannya lurus: dengan index, hanya 5 kunci diperiksa dan 5 document dibaca untuk 5 hasil. Tidak ada pembacaan yang sia-sia — itulah ciri query yang sehat.
Empat Metrik Utama #
| Metrik | Arti | Cara membacanya |
|---|---|---|
nReturned | Jumlah document yang lolos ke hasil | Harus dekat dengan ekspektasi bisnis query |
totalKeysExamined | Total kunci index yang diperiksa semua tahap | 0 berarti tidak ada index yang terpakai |
totalDocsExamined | Total document yang dibaca dari collection | Sumber biaya terbesar pada data besar |
executionTimeMillis | Total waktu eksekusi server (milidetik) | Acuan kasar; sangat dipengaruhi cache dan beban |
Dua metrik pertama adalah pasangan diagnosis: totalKeysExamined yang besar dengan nReturned kecil berarti index yang dipakai tidak selektif — rentang indexBounds-nya terlalu lebar. Sementara totalDocsExamined yang jauh di atas nReturned menandakan server membaca banyak document yang akhirnya dibuang filter — indikasi index yang tidak cocok atau query yang tidak bisa memanfaatkan index.
executionStages memuat versi per-tahap dari angka-angka tersebut: setiap stage (FETCH, IXSCAN, SORT) punya nReturned, keysExamined, dan docsExamined sendiri. Total di level atas adalah penjumlahannya — struktur pohonnya kita bedah tuntas di Chapter berikutnya.
Rasio Efisiensi #
Cara paling cepat menilai sebuah rencana adalah membandingkan ketiga angka tersebut dalam bentuk rasio. Kondisi ideal untuk query ber-index adalah nReturned : totalKeysExamined : totalDocsExamined = 1 : 1 : 1 — setiap pemeriksaan menghasilkan satu document yang memang dikirim ke klien. Mari kita hitung untuk kedua rencana tadi:
const tanpaIndex = { nReturned: 5, totalKeysExamined: 0, totalDocsExamined: 12 }
const denganIndex = db.penjualan.find({ kota: "Jakarta" }).explain("executionStats").executionStats
print("COLLSCAN -> nReturned:", tanpaIndex.nReturned, "| keys:", tanpaIndex.totalKeysExamined, "| docs:", tanpaIndex.totalDocsExamined)
print("IXSCAN -> nReturned:", denganIndex.nReturned, "| keys:", denganIndex.totalKeysExamined, "| docs:", denganIndex.totalDocsExamined)
print("rasio docs/hasil -> COLLSCAN:", (tanpaIndex.totalDocsExamined / tanpaIndex.nReturned).toFixed(2), "| IXSCAN:", (denganIndex.totalDocsExamined / denganIndex.nReturned).toFixed(2))Output yang Dihasilkan:
COLLSCAN -> nReturned: 5 | keys: 0 | docs: 12
IXSCAN -> nReturned: 5 | keys: 5 | docs: 5
rasio docs/hasil -> COLLSCAN: 2.40 | IXSCAN: 1.00Rasio totalDocsExamined / nReturned pada COLLSCAN adalah 2,40 — setiap hasil menanggung pembacaan 2,4 document. Angka ini terlihat jinak karena collection kita kecil; pada satu juta document dengan sebaran kota yang sama, rasionya menjadi sekitar 200.000 dan query mulai terasa berat. Rasio 1,00 pada IXSCAN tidak pernah berubah sebesar apa pun collection — itulah alasan index disebut penskala: biayanya tumbuh mengikuti jumlah hasil, bukan jumlah data.
Rasio yang sehat menurut pengalaman praktis:
| Kondisi | nReturned : keys : docs | Tindakan |
|---|---|---|
| Ideal | 1 : 1 : 1 | Tidak ada yang perlu dibenahi |
| Wajar | 1 : beberapa : 1 | Index masih selektif; pantau bila data membesar |
| Boros | 1 : banyak : banyak | Perbaiki index atau bentuk query |
| Tanpa index | 1 : 0 : semua | Buat index pada field filter |
Rangkuman #
explain("executionStats")mengeksekusi query sungguhan dan melaporkan biayanya; modequeryPlannerhanya menunjukkan rencana.- Empat angka kunci:
nReturned,totalKeysExamined,totalDocsExamined,executionTimeMillis— dua yang tengah adalah yang paling bisa diandalkan. - Rasio ideal ber-index adalah 1 : 1 : 1;
COLLSCANselalu berakhirtotalDocsExamined= seluruh collection. - Waktu eksekusi pada dataset kecil tidak bisa dijadikan bukti; biaya pemeriksaan adalah penentu yang stabil.