Tidak semua document di collection bernilai sama bagi query. Buku yang stoknya habis jarang dicari pembeli, transaksi yang sudah selesai jarang dibuka lagi — namun index biasa tetap mencatat semuanya tanpa kecuali. Partial index mengizinkan kita menyeleksi: hanya document yang memenuhi kondisi tertentu yang masuk index. Hasilnya index lebih kecil, tulis lebih ringan, dan query panas tetap cepat.
Peta materi Chapter ini:
- Membuat partial index dengan
partialFilterExpression. - Syarat query agar bisa memakai partial index.
- Kombinasi
unique+partialuntuk field opsional seperti email. - Posisi partial index dibanding sparse index.
Index Hanya untuk Document Terpilih #
Opsi partialFilterExpression menerima filter query yang menentukan siapa saja yang boleh masuk index. Siapkan katalog yang sebagian bukunya kehabisan stok:
use latihan_index
db.buku.drop()
db.buku.insertMany([
{ judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "fiksi", stok: 12 },
{ judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "fiksi", stok: 25 },
{ judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "nonfiksi", stok: 0 },
{ judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi", stok: 9 },
{ judul: "Hujan Bulan Juni", penulis: "Sapardi Djoko Damono", harga: 65000, kategori: "puisi", stok: 8 },
{ judul: "Ensiklopedia Sains Populer", penulis: "Tim Sains", harga: 250000, kategori: "referensi", stok: 0 }
])
db.buku.createIndex({ harga: 1 }, { partialFilterExpression: { stok: { $gt: 0 } } })Output yang Dihasilkan:
switched to db latihan_index
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3bd5ba3a7ad00ef8340c0'),
'1': ObjectId('6ad3bd5ba3a7ad00ef8340c1'),
'2': ObjectId('6ad3bd5ba3a7ad00ef8340c2'),
'3': ObjectId('6ad3bd5ba3a7ad00ef8340c3'),
'4': ObjectId('6ad3bd5ba3a7ad00ef8340c4'),
'5': ObjectId('6ad3bd5ba3a7ad00ef8340c5')
}
}
harga_1Index harga_1 kini hanya berisi empat entri — buku berstok nol (“Filosofi Teras” dan “Ensiklopedia Sains Populer”) tidak pernah masuk. Mari uji bagaimana planner memperlakukan dua query yang mirip:
function ringkas(kueri) {
const tahap = kueri.queryPlanner.winningPlan
return tahap.inputStage ? tahap.inputStage.stage + " lewat " + tahap.inputStage.indexName : tahap.stage
}
printjson({
denganFilterStok: ringkas(db.buku.find({ stok: { $gt: 0 }, harga: { $lt: 90000 } }).explain()),
tanpaFilterStok: ringkas(db.buku.find({ harga: { $lt: 90000 } }).explain())
})Output yang Dihasilkan:
[Function: ringkas]
{
denganFilterStok: 'IXSCAN lewat harga_1',
tanpaFilterStok: 'COLLSCAN'
}Perbedaan keduanya tegas. Query pertama menyertakan stok: { $gt: 0 } — kondisi yang sama dengan partial filter — sehingga planner yakin hasilnya identik dengan isi index dan memilih IXSCAN. Query kedua hanya menyaring harga; karena index tidak memuat buku berstok nol, memakai index berisiko kehilangan hasil, maka planner mundur ke COLLSCAN.
Kombinasi Unique dan Partial #
Gabungan paling populer dari partial index adalah unique + partial untuk field opsional. Anggota toko buku boleh tidak punya email, tetapi email yang terdaftar tidak boleh kembar:
db.anggota.drop()
db.anggota.insertMany([
{ nama: "Budi", email: "[email protected]" },
{ nama: "Siti" },
{ nama: "Dewi" }
])
db.anggota.createIndex(
{ email: 1 },
{ unique: true, partialFilterExpression: { email: { $exists: true } } }
)
db.anggota.insertOne({ nama: "Eka" })
try {
db.anggota.insertOne({ nama: "Fajar", email: "[email protected]" })
} catch (e) {
print("Kode galat: " + e.code)
print("Pesan: " + e.message)
}Output yang Dihasilkan:
true
{
acknowledged: true,
insertedIds: {
'0': ObjectId('6ad3bd5ba3a7ad00ef8340c6'),
'1': ObjectId('6ad3bd5ba3a7ad00ef8340c7'),
'2': ObjectId('6ad3bd5ba3a7ad00ef8340c8')
}
}
email_1
{
acknowledged: true,
insertedId: ObjectId('6ad3bd5ba3a7ad00ef8340c9')
}
Kode galat: 11000
Pesan: E11000 duplicate key error collection: latihan_index.anggota index: email_1 dup key: { email: "[email protected]" }Empat document tanpa email hidup berdampingan tanpa masalah — mereka berada di luar jangkauan index. Sebaliknya, email [email protected] yang coba didaftarkan Fajar ditolak dengan galat E11000 karena keunikan hanya ditegakkan pada document yang memiliki field email. Solusi klasik masalah “field unik yang opsional” berakhir tanpa trik apa pun.
Partial atau Sparse #
MongoDB punya satu fitur lawas yang mirip: sparse index, yang hanya meng-index document yang memiliki field terkait (dibahas penuh di Chapter Sparse Index). Tabel ringkasnya:
| Aspek | Partial index | Sparse index |
|---|---|---|
| Kriteria masuk index | Filter bebas: $gt, $exists, $eq, $and, tipe field | Satu syarat saja: field ada |
| Presisi seleksi | Tinggi — bisa stok > 0, status: "aktif" | Rendah — hanya ada/tidaknya field |
| Dokumentasi resmi | Direkomendasikan | Digantikan posisinya oleh partial |
Dokumentasi MongoDB menyatakan partial index unggul dalam kontrol: sparse hanya tahu “field ini ada”, partial bisa menyaring “field ini ada dan nilainya memenuhi kondisi”. Aturan praktisnya sederhana — untuk index baru yang butuh seleksi, pilih partialFilterExpression; sparse tinggal dikenali pada sistem lama.
Rangkuman #
createIndex({ field: 1 }, { partialFilterExpression: { ... } })hanya meng-index document yang lolos filter — index lebih kecil dan tulis lebih ringan.- Query harus memuat kondisi yang sama agar planner mau memakai partial index; tanpa itu, planner memilih
COLLSCANdemi kebenaran hasil. - Kombinasi
unique+partialmenyelesaikan pola “field unik opsional”: document tanpa field bebas berjumlah banyak, yang punya field dijaga keunikannya. - Dokumentasi resmi merekomendasikan partial index menggantikan sparse index untuk kebutuhan seleksi apa pun.