Sembilan produk membuat semua query terasa instan — tetapi nomor itu menipu. Tanpa index, setiap find() memindai seluruh collection dari awal (COLLSCAN), dan biaya scanning tumbuh linear mengikuti jumlah produk. Pada bagian ini kita memasang tiga index yang menyasar tiga query terpenting gudang: pencarian produk per sku, filter kategori dengan rentang harga, dan pencarian bebas berdasarkan nama.
Tiga Index untuk Tiga Query #
| Query aplikasi | Index | Jenis |
|---|---|---|
Detail produk per sku | { sku: 1 } | Unique — sekaligus penjaga integritas data |
| Katalog per kategori, disaring rentang harga | { kategori: 1, harga: 1 } | Compound — equality di depan, range di belakang |
| Pencarian bebas nama produk | { nama: "text" } | Text — dengan skor relevansi |
Urutan field pada index compound mengikuti aturan equality-sort-range dari chapter Compound Index: kategori difilter tepat (equality) sehingga berada di posisi pertama, harga dipakai rentang (range) sehingga menyusul di belakang. Index yang sama sekaligus melayani sort({ harga: 1 }) di dalam satu kategori tanpa tahap pengurutan terpisah.
Skrip Index dan Pencarian #
// inventaris-index.js — index dan pencarian produk
// Jalankan setelah inventaris-crud.js:
// mongosh "mongodb://127.0.0.1:27017/?directConnection=true" --file src/inventaris-index.js
var inv = db.getSiblingDB("inventaris")
function buatIndex() {
inv.produk.createIndex({ sku: 1 }, { unique: true })
inv.produk.createIndex({ kategori: 1, harga: 1 })
inv.produk.createIndex({ nama: "text" })
print("index siap:", inv.produk.getIndexes()
.map(function (i) { return i.name })
.join(", "))
}
function ujiIndexUnik() {
let kode = null
try {
inv.produk.insertOne({ sku: "KB-001", nama: "duplikat" })
} catch (galat) {
kode = galat.code
}
print("insert duplikat sku ditolak, kode:", kode)
}
function cariPerSku(sku) {
print("-- cari per sku:", sku)
printjson(inv.produk.findOne(
{ sku: sku },
{ _id: 0, sku: 1, nama: 1, harga: 1, stok: 1 }
))
}
function cariKategoriRentang(kategori, hargaMin, hargaMax) {
print("--", kategori, "| harga", hargaMin, "sampai", hargaMax)
inv.produk
.find({ kategori: kategori, harga: { $gte: hargaMin, $lte: hargaMax } })
.sort({ harga: 1 })
.projection({ _id: 0, sku: 1, nama: 1, harga: 1 })
.forEach(function (p) { print(" ", p.sku, "|", p.nama, "|", p.harga) })
}
function cariTeks(frase) {
print("-- pencarian teks:", frase)
inv.produk
.find(
{ $text: { $search: frase } },
{ skor: { $meta: "textScore" }, _id: 0, sku: 1, nama: 1 }
)
.sort({ skor: { $meta: "textScore" } })
.forEach(function (p) { print(" ", p.sku, "|", p.nama, "| skor", p.skor.toFixed(4)) })
}
function rencanaQuery() {
const rencana = inv.produk
.find({ kategori: "keyboard", harga: { $gte: 100000, $lte: 1000000 } })
.sort({ harga: 1 })
.explain()
printjson(rencana.queryPlanner.winningPlan)
}
buatIndex()
ujiIndexUnik()
cariPerSku("MN-002")
cariKategoriRentang("keyboard", 100000, 1000000)
cariTeks("monitor ips")
rencanaQuery()Jalankan setelah skrip CRUD:
mongosh "mongodb://127.0.0.1:27017/?directConnection=true" --file src/inventaris-index.jsOutput yang Dihasilkan:
index siap: _id_, sku_1, kategori_1_harga_1, nama_text
insert duplikat sku ditolak, kode: 11000
-- cari per sku: MN-002
{
sku: 'MN-002',
nama: 'Monitor VA 27 inci',
harga: 2400000,
stok: 15
}
-- keyboard | harga 100000 sampai 1000000
KB-001 | Keyboard Mekanik 60% | 750000
KB-004 | Keyboard Tenkeyless Hot-swap | 950000
-- pencarian teks: monitor ips
MN-001 | Monitor IPS 24 inci | skor 1.2500
MN-002 | Monitor VA 27 inci | skor 0.6250
{
isCached: false,
stage: 'FETCH',
nss: 'inventaris.produk',
inputStage: {
stage: 'IXSCAN',
nss: 'inventaris.produk',
keyPattern: {
kategori: 1,
harga: 1
},
indexName: 'kategori_1_harga_1',
isMultiKey: false,
multiKeyPaths: {
kategori: [],
harga: []
},
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: 'forward',
indexBounds: {
kategori: [
'["keyboard", "keyboard"]'
],
harga: [
'[100000, 1000000]'
]
}
}
}Membaca Bukti dari Output #
ujiIndexUnik()mencoba menyisipkan produk denganskuyang sudah ada. Index unik menolak operasi tersebut dengan kode11000— duplikat tertangani di sisi server, melengkapi penjagafindOne()pada Bagian 2. Kata kuncitry/catchdiperlukan karena tanpa penangkapan, skrip akan berhenti di titik tersebut.cariKategoriRentang()menampilkan dua keyboard di rentang Rp100.000–Rp1.000.000 dalam keadaan terurut. Keterurutan tersebut datang gratis dari index — tidak ada tahap SORT terpisah.cariTeks("monitor ips")memasangkan dua operator$meta: "textScore": satu di projection agar skor ikut terbaca, satu di sort agar hasil diurutkan dari paling relevan. Monitor IPS mencetak skor1.2500karena kedua kata pencarian cocok; Monitor VA hanya cocok pada katamonitorsehingga skornya setengah.rencanaQuery()membuka kartu rencana query lewatexplain(). TahapIXSCANdengankeyPattern { kategori: 1, harga: 1 }membuktikan index compound terpakai, danindexBoundsmemperlihatkan batas pencarian yang sempit —kategoriterkunci pada satu nilai,hargadibatasi rentang. TahapFETCHdi atasnya hanya mengambil document lengkap untuk dua kandidat hasil.
Apa Berikutnya #
Query utama kini berjalan lewat index dan integritas sku terjaga otomatis. Bagian 4 menutup proyek dengan tiga laporan aggregation — nilai persediaan, stok menipis, dan rekap pergerakan bulanan — lalu memverifikasi seluruh perilaku proyek melalui suite tes.