Dua collection bisa sama-sama memiliki index, tetapi index yang satu memangkas 99% kandidat dalam sekali lompat sementara yang lain hanya menyisihkan sedikit. Kualitas pembeda itu bernama selectivity — daya pangkas sebuah index. Memilih field yang salah untuk di-index membuat B-tree rapi tersimpan namun nyaris tak mempercepat apa pun; memilih yang tepat membuat setiap pemindaian superpendek. Chapter ini mengukur selectivity secara konkret dengan data kita sendiri.
Peta materi Chapter ini:
- Definisi selectivity sebagai rasio nilai berbeda terhadap total document.
- Membuktikan field boolean sebagai index berselectivity rendah.
- Menaikkan selectivity lewat kombinasi field.
Mengukur Selectivity #
Cara termudah menaksir selectivity sebuah field: bandingkan jumlah nilai berbeda dengan jumlah total document. Mari hitung untuk tiga field pada katalog delapan buku kita:
use latihan_index
db.buku.drop()
db.buku.insertMany([
{ judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "fiksi", aktif: true },
{ judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "fiksi", aktif: true },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "nonfiksi", aktif: true },
{ judul: "Atomic Habits", penulis: "James Clear", harga: 115000, kategori: "nonfiksi", aktif: false },
{ judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi", aktif: true },
{ judul: "Bumi Manusia", penulis: "Pramoedya A. Toer", harga: 105000, kategori: "sastra", aktif: true },
{ judul: "Hujan Bulan Juni", penulis: "Sapardi Djoko Damono", harga: 65000, kategori: "puisi", aktif: false },
{ judul: "Ensiklopedia Sains Populer", penulis: "Tim Sains", harga: 250000, kategori: "referensi", aktif: true }
])
printjson({
totalDocument: db.buku.countDocuments({}),
nilaiKategori: db.buku.distinct("kategori").length,
nilaiJudul: db.buku.distinct("judul").length,
nilaiAktif: db.buku.distinct("aktif").length
})Output yang Dihasilkan:
switched to db latihan_index
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3be9e07abe250a98a1580'),
'1': ObjectId('6ad3be9e07abe250a98a1581'),
'2': ObjectId('6ad3be9e07abe250a98a1582'),
'3': ObjectId('6ad3be9e07abe250a98a1583'),
'4': ObjectId('6ad3be9e07abe250a98a1584'),
'5': ObjectId('6ad3be9e07abe250a98a1585'),
'6': ObjectId('6ad3be9e07abe250a98a1586'),
'7': ObjectId('6ad3be9e07abe250a98a1587')
}
}
{
totalDocument: 8,
nilaiKategori: 5,
nilaiJudul: 8,
nilaiAktif: 2
}Angka-angka itu menghasilkan peringkat selectivity berikut:
| Field | Nilai berbeda | Rasio | Selectivity |
|---|---|---|---|
judul | 8 dari 8 | 1,00 | Tinggi — hampir setiap query menyisakan satu document |
kategori | 5 dari 8 | 0,63 | Sedang — “fiksi” menyisakan tiga document |
aktif | 2 dari 8 | 0,25 | Rendah — satu nilai mencakup enam document |
Rasio tinggi berarti nilai query rata-rata memangkas hampir seluruh collection; rasio rendah berarti index hanya membagi data menjadi dua tumpukan besar.
Field Boolean yang Tidak Layak Di-index Sendirian #
Bukti dari medan: paksa planner memakai ketiga index lewat hint(), lalu bandingkan apa yang diperiksanya. Fungsi statistik() di bawah ini membaca metrik dari executionStats:
db.buku.createIndex({ judul: 1 })
db.buku.createIndex({ kategori: 1 })
db.buku.createIndex({ aktif: 1 })
function statistik(rencana) {
const s = rencana.executionStats
return { hasil: s.nReturned, kunciDiperiksa: s.totalKeysExamined }
}
printjson({
judulTepat: statistik(db.buku.find({ judul: "Bumi" }).hint({ judul: 1 }).explain("executionStats")),
kategoriFiksi: statistik(db.buku.find({ kategori: "fiksi" }).hint({ kategori: 1 }).explain("executionStats")),
aktifTrue: statistik(db.buku.find({ aktif: true }).hint({ aktif: 1 }).explain("executionStats"))
})Output yang Dihasilkan:
judul_1
kategori_1
aktif_1
[Function: statistik]
{
judulTepat: {
hasil: 1,
kunciDiperiksa: 1
},
kategoriFiksi: {
hasil: 3,
kunciDiperiksa: 3
},
aktifTrue: {
hasil: 6,
kunciDiperiksa: 6
}
}Query judul memeriksa satu kunci untuk satu hasil — pemangkasan maksimal. Query aktif: true memeriksa enam kunci untuk enam hasil: hampir seluruh collection lolos saringan. Pada delapan document hal itu tak terasa, tetapi bayangkan distribusi yang sama pada satu juta document — index aktif tetap memindai sekitar 750 ribu entri untuk setiap query aktif: true. Field boolean, gender, dan status dua-nilai memang pilihan index yang buruk bila berdiri sendiri.
Menaikkan Selectivity dengan Kombinasi #
Selectivity tidak berhenti pada satu field — kombinasi field mengalikan daya pangkasnya. Kategori “fiksi” menyisakan tiga buku dan harga 89000 menyisakan dua; gabungan keduanya menyisakan tepat satu. Mari hitung jumlah kombinasi berbeda lewat aggregation, lalu uji compound index yang menemaninya:
printjson(db.buku.aggregate([
{ $group: { _id: { kategori: "$kategori", harga: "$harga" } } },
{ $count: "kombinasiBerbeda" }
]).toArray())
db.buku.createIndex({ kategori: 1, harga: 1 })
printjson(statistik(db.buku.find({ kategori: "fiksi", harga: 89000 }).explain("executionStats")))Output yang Dihasilkan:
[
{
kombinasiBerbeda: 8
}
]
kategori_1_harga_1
{
hasil: 1,
kunciDiperiksa: 1
}Pipeline $group + $count melaporkan delapan kombinasi berbeda dari delapan document — rasio gabungan 8/8, naik dari 5/8 milik kategori saja. Query fiksi + 89000 pada compound index memeriksa satu kunci dan mengembalikan satu hasil. Inilah mesin di balik pola ESR dari Chapter Compound Index: field equality yang selektif diletakkan di depan agar pemangkasan terjadi sedini mungkin.
Rangkuman #
- Selectivity = rasio nilai berbeda terhadap total document; ukur cepat dengan
distinct().lengthdibagicountDocuments(). - Field boolean (2 nilai) berselectivity rendah — index-nya memeriksa hampir seluruh entri untuk setiap query.
- Kombinasi field menaikkan selectivity:
kategori(5/8) +hargamenghasilkan 8/8 kombinasi unik. - Letakkan field paling selektif di posisi terdepan compound index — selaras dengan pola ESR.