Setiap index yang kita buat menulis dirinya sendiri pada setiap insert, update, dan delete — biaya yang dibayar tanpa kecuali. Imbalannya hanya datang kalau index itu benar-benar dipakai query. Dari sekian index yang menumpuk di collection produksi, hampir selalu ada yang sudah lama tidak melayani apa pun: sisa eksperimen, query yang sudah berubah, atau fitur yang hilang. MongoDB mencatat pemakaian setiap index lewat tahap agregasi $indexStats, sehingga keputusan menjatuhkan index bisa berbasis data, bukan firasat.
Peta materi Chapter ini:
- Membaca
$indexStats:accesses.opsdanaccesses.since. - Menggabungkannya dengan
getIndexes()menjadi laporan satu layar. - Pertimbangan sebelum menjatuhkan index yang tampak tak terpakai.
Membaca $indexStats #
$indexStats adalah tahap agregasi yang mengembalikan satu document per index. Siapkan collection dengan tiga index, lalu gunakan dua di antaranya:
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" }
])
db.penjualan.createIndex({ kota: 1 })
db.penjualan.createIndex({ channel: 1 })
db.penjualan.createIndex({ harga_total: 1 })
db.penjualan.find({ kota: "Jakarta" }).itcount()
db.penjualan.find({ channel: "online" }).itcount()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
}
}
kota_1
channel_1
harga_total_1
5
6Dua query di akhir blok sengaja memakai index kota_1 dan channel_1; index harga_total_1 tidak pernah disentuh. Sekarang kita baca catatan pemakaiannya:
db.penjualan.aggregate([ { $indexStats: {} } ])Output yang Dihasilkan:
[
{
name: 'channel_1',
key: { channel: 1 },
host: 'c-6ad366ea-145cf406-1e00df7b3d2b:27017',
accesses: { ops: Long('1'), since: ISODate('2026-10-17T17:40:35.455Z') }
},
{
name: '_id_',
key: { _id: 1 },
host: 'c-6ad366ea-145cf406-1e00df7b3d2b:27017',
accesses: { ops: Long('0'), since: ISODate('2026-10-17T17:40:35.336Z') }
},
{
name: 'kota_1',
key: { kota: 1 },
host: 'c-6ad366ea-145cf406-1e00df7b3d2b:27017',
accesses: { ops: Long('1'), since: ISODate('2026-10-17T17:40:35.360Z') }
},
{
name: 'harga_total_1',
key: { harga_total: 1 },
host: 'c-6ad366ea-145cf406-1e00df7b3d2b:27017',
accesses: { ops: Long('0'), since: ISODate('2026-10-17T17:40:35.558Z') }
}
]Field spec pada tiap entri dipangkas dari output asli agar ringkas; nilai since dan host di server Anda tentu berbeda, dan hasil runCommand() yang tampil di chapter ini juga dipadatkan dengan membuang metadata waktu replikasi. Field yang penting untuk dibaca:
accesses.ops: berapa kali index ini dipakai oleh query sejak penghitung dimulai. Angka ini mengukur pemakaian pembacaan — index tetap ditulis pada setiap operasi tulis meskiopsbernilai 0, jadi biaya maintenance tidak tercermin di sini.accesses.since: titik waktu mulai penghitung. Penghitung dimulai saat index dibuat dan kembali nol saat server direstart — dua index yang dibuat bersamaan bisa tampak berbeda pemakaiannya semata karena umur penghitungnya berbeda.host: nama anggota server yang melaporkan. Pada replica set, setiap anggota punya catatannya sendiri — jalankan di semua anggota (termasuk secondary yang melayani query baca) sebelum menyimpulkan sebuah index tak terpakai.
Pada output di atas polanya jelas: channel_1 dan kota_1 menunjukkan ops di atas nol, sementara harga_total_1 bernilai 0 — kandidat kuat index menganggur. Index _id_ juga bernilai 0, tetapi index ini wajib ada dan tidak bisa dijatuhkan.
Laporan Satu Layar #
$indexStats sendiri tidak menampilkan definisi index, sedangkan getIndexes() tidak menampilkan pemakaian — laporan yang lengkap menggabungkan keduanya. Sedikit JavaScript di mongosh merangkainya:
const pemakaian = new Map(
db.penjualan.aggregate([ { $indexStats: {} } ]).toArray().map(s => [s.name, s.accesses.ops])
)
for (const idx of db.penjualan.getIndexes()) {
print(idx.name.padEnd(16), "ops:", String(pemakaian.get(idx.name)))
}Output yang Dihasilkan:
_id_ ops: 0
kota_1 ops: 1
channel_1 ops: 1
harga_total_1 ops: 0Laporan seperti ini layak dijalankan berkala — misalnya setiap kuartal — dan hasilnya disimpan sebagai bahan pembanding. Menjatuhkan index yang bersalah:
db.penjualan.dropIndex("harga_total_1")Output yang Dihasilkan:
{ nIndexesWas: 4, ok: 1 }Pertimbangan Sebelum Menjatuhkan Index #
Lima hal yang perlu dicek sebelum dropIndex() dijalankan:
| Pertimbangan | Alasannya |
|---|---|
| Jendela pengamatan cukup panjang | Query berkala (laporan, batch malam) bisa memakai index jarang tapi kritis |
Bukan index unique | Index unik menegakkan aturan data, bukan hanya mempercepat query — menjatuhkannya mengubah jaminan |
| Bukan index TTL | Index TTL menjalankan pembersihan document otomatis; pemakaian rendah memang sifatnya |
| Definisi tersimpan | Simpan output getIndexes() sebelum drop agar index mudah dibuat ulang bila ternyata dibutuhkan |
| Dampak tulis terukur | Keuntungan drop paling terasa pada collection dengan beban tulis tinggi; pada collection jarang ditulis, index menganggur tidak terlalu merugikan |
Membuat ulang index pada collection besar adalah operasi mahal yang memakan waktu dan sumber daya — di cluster produksi lazimnya dibangun ulang di latar belakang dengan opsi build tertentu. Itulah sebabnya urutan kerjanya: amati kuartalan, simpan definisi, jatuhkan, lalu pantau beberapa hari pertama setelahnya.
Rangkuman #
$indexStatsmelaporkanaccesses.ops(jumlah pemakaian oleh query) danaccesses.since(awal penghitung) untuk setiap index.- Penghitung reset saat server restart;
opstidak mengukur biaya maintenance tulis yang tetap berjalan untuk setiap index. - Gabungkan
$indexStatsdengangetIndexes()untuk laporan definisi plus pemakaian dalam satu layar. - Ukur pemakaian minimal satu kuartal, periksa status
unique/TTL, dan simpan definisi sebelum menjatuhkan index.