Query harga di Chapter sebelumnya berubah drastis setelah satu baris createIndex(). Bentuk index paling sederhana — dan yang akan kita pakai paling sering — adalah single field index: index pada tepat satu field. Chapter ini membedah cara membuatnya, struktur B-tree yang menyimpan nilainya, arti arah 1 dan -1, cara memeriksa dan menghapus index, serta index _id yang selalu hadir otomatis di setiap collection.

Peta materi Chapter ini:

  • createIndex() satu field beserta pemeriksaan lewat getIndexes().
  • Struktur B-tree: kenapa nilai terurut membuat pencarian cepat.
  • Arah 1 dan -1 pada single field index.
  • dropIndex() dan index _id yang tidak bisa dihapus.

Membuat dan Memeriksa Index #

Gunakan kembali katalog delapan buku kita:

js
use latihan_index
db.buku.drop()
db.buku.insertMany([
  { judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "fiksi", tag: ["fiksi", "sastra", "penghargaan"], stok: 12 },
  { judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "fiksi", tag: ["fiksi", "remaja", "fantasi"], stok: 25 },
  { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "nonfiksi", tag: ["filsafat", "pengembangan diri"], stok: 18 },
  { judul: "Atomic Habits", penulis: "James Clear", harga: 115000, kategori: "nonfiksi", tag: ["pengembangan diri", "terjemahan"], stok: 30 },
  { judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi", tag: ["fiksi", "remaja"], stok: 9 },
  { judul: "Bumi Manusia", penulis: "Pramoedya A. Toer", harga: 105000, kategori: "sastra", tag: ["sastra", "sejarah"], stok: 15 },
  { judul: "Hujan Bulan Juni", penulis: "Sapardi Djoko Damono", harga: 65000, kategori: "puisi", tag: ["puisi", "sastra"], stok: 8 },
  { judul: "Ensiklopedia Sains Populer", penulis: "Tim Sains", harga: 250000, kategori: "referensi", tag: ["referensi", "sains"], stok: 4 }
])

Output yang Dihasilkan:

text
switched to db latihan_index
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3b359fa2dc9c2fb4c00e9'),
    '1': ObjectId('6ad3b359fa2dc9c2fb4c00ea'),
    '2': ObjectId('6ad3b359fa2dc9c2fb4c00eb'),
    '3': ObjectId('6ad3b359fa2dc9c2fb4c00ec'),
    '4': ObjectId('6ad3b359fa2dc9c2fb4c00ed'),
    '5': ObjectId('6ad3b359fa2dc9c2fb4c00ee'),
    '6': ObjectId('6ad3b359fa2dc9c2fb4c00ef'),
    '7': ObjectId('6ad3b359fa2dc9c2fb4c00f0')
  }
}

createIndex() menerima object yang memetakan field ke arah urutan: 1 menaik, -1 menurun. Method ini mengembalikan nama index yang terbentuk, lalu getIndexes() memperlihatkan seluruh index pada collection:

js
db.buku.createIndex({ harga: 1 })
db.buku.getIndexes()

Output yang Dihasilkan:

text
harga_1
[
  {
    v: 2,
    key: { _id: 1 },
    name: '_id_',
    collation: { locale: 'simple' }
  },
  {
    v: 2,
    key: { harga: 1 },
    name: 'harga_1',
    collation: { locale: 'simple' }
  }
]

Dua entry tersebut perlu dibedah:

  1. _id_: index bawaan yang dibuat MongoDB secara otomatis untuk setiap collection, atas field _id. Kita tidak pernah membuatnya secara manual.
  2. harga_1: index baru kita. Penamaan defaultnya adalah nama field diikuti arah — harga + _1. Opsi name untuk memberi nama sendiri dibahas di Chapter Index Properties.
  3. key: { harga: 1 }: pola (key pattern) index — field apa yang di-index dan dengan arah apa.

Query pada field harga kini memakai index. Rencana eksekusinya menunjukkan tahap IXSCAN dengan batas pencarian yang sempit:

js
db.buku.find({ harga: 89000 }, { _id: 0, judul: 1, harga: 1 })
const rencana = db.buku.find({ harga: 89000 }).explain()
printjson(rencana.queryPlanner.winningPlan.inputStage)

Output yang Dihasilkan:

text
[
  { judul: 'Bumi', harga: 89000 },
  { judul: 'Filosofi Teras', harga: 89000 }
]

{
  stage: 'IXSCAN',
  nss: 'latihan_index.buku',
  keyPattern: {
    harga: 1
  },
  indexName: 'harga_1',
  isMultiKey: false,
  multiKeyPaths: {
    harga: []
  },
  isUnique: false,
  isSparse: false,
  isPartial: false,
  indexVersion: 2,
  direction: 'forward',
  indexBounds: {
    harga: [
      '[89000, 89000]'
    ]
  }
}

Field indexBounds adalah bukti langsung manfaat B-tree: pencarian terkunci pada rentang [89000, 89000], bukan seluruh collection. Document lain tidak pernah disentuh.

Struktur B-tree di Balik Layar #

Semua index reguler MongoDB disimpan sebagai B-tree — pohon yang selalu seimbang dengan banyak cabang pada tiap simpulnya. Nilai field terurut dari kiri ke kanan, dan setiap entri menyimpan pasangan kunci → penunjuk document.

Diagram di atas memperlihatkan isi konseptual index harga_1 (nilai lain berada di simpul yang tidak digambar). Mencari 89000 berarti: bandingkan dengan akar (85000), ambil cabang kanan, temukan 89000 — dua langkah, berapa pun jumlah document di bawahnya. Itulah pertumbuhan logaritmik yang kita hitung di Chapter sebelumnya: menambah ribuan judul baru hanya menambah satu atau dua tingkat pohon.

Dua konsekuensi struktur ini layak diingat. Pertama, index menyimpan nilai field — bukan document — sehingga ukurannya ditentukan ukuran nilai, bukan ukuran document penuh. Kedua, karena nilai terurut, index juga siap melayani pengurutan dan rentang ($gt, $lt, $gte, $lte) tanpa kerja tambahan.

Arah 1 dan -1 pada Single Field #

Untuk index satu field, arah 1 atau -1 tidak memengaruhi query filter — pencarian nilai tepat sama cepat di kedua arah. Arah baru terasa saat melayani sort(). Index { harga: 1 } menyimpan nilai menaik, tetapi MongoDB dapat memindainya mundur untuk menghasilkan urutan menurun:

js
const rencana = db.buku.find({}, { _id: 0, judul: 1, harga: 1 }).sort({ harga: -1 }).explain()
printjson(rencana.queryPlanner.winningPlan.inputStage.inputStage)

Output yang Dihasilkan:

text
{
  stage: 'IXSCAN',
  nss: 'latihan_index.buku',
  keyPattern: {
    harga: 1
  },
  indexName: 'harga_1',
  isMultiKey: false,
  multiKeyPaths: {
    harga: []
  },
  isUnique: false,
  isSparse: false,
  isPartial: false,
  indexVersion: 2,
  direction: 'backward',
  indexBounds: {
    harga: [
      '[MaxKey, MinKey]'
    ]
  }
}

Perhatikan direction: 'backward' — index dipindai dari ujung besar ke ujung kecil, sehingga hasil sudah terurut menurun tanpa tahap SORT di memori. Kesimpulan praktisnya sederhana namun penting:

KebutuhanIndex yang perlu dibuat
Filter harga (nilai tepat atau rentang){ harga: 1 } atau { harga: -1 } — sama saja
sort({ harga: 1 }){ harga: 1 } (atau { harga: -1 } dipindai mundur)
sort({ harga: -1 }){ harga: 1 } (dipindai mundur) — cukup satu index

Satu index { harga: 1 } melayani kedua arah sort. Arah baru menjadi krusial pada index multi-field — dibahas khusus di Chapter Compound Index dan Index Ordering.

Menghapus Index dan Index _id #

Index yang tidak terpakai sebaiknya dihapus agar tidak menambah biaya tulis. dropIndex() menerima nama index — ambil dari getIndexes():

js
const hasil = db.buku.dropIndex("harga_1")
printjson({ jumlahIndexSebelumnya: hasil.nIndexesWas, status: hasil.ok })
db.buku.getIndexes()

Output yang Dihasilkan:

text
{ jumlahIndexSebelumnya: 2, status: 1 }

[
  {
    v: 2,
    key: { _id: 1 },
    name: '_id_',
    collation: { locale: 'simple' }
  }
]

Nilai jumlahIndexSebelumnya: 2 mencatat jumlah index sebelum penghapusan — setelahnya tersisa satu. Yang tersisa itu, _id_, tidak bisa dihapus:

js
try {
  db.buku.dropIndex("_id_")
} catch (e) {
  print("Galat: " + e.message)
}

Output yang Dihasilkan:

text
Galat: cannot drop _id index

Rangkuman #

  • db.collection.createIndex({ field: 1 }) membuat single field index; getIndexes() mendaftarkan semua index beserta key pattern dan namanya.
  • Nama default index adalah <field>_<arah>, misalnya harga_1; index _id_ selalu ada otomatis dan tidak dapat dihapus.
  • Index disimpan sebagai B-tree terurut: pencarian nilai, rentang, dan sort sama-sama diuntungkan.
  • Pada single field index, arah 1/-1 tidak memengaruhi filter; satu index melayani sort dua arah lewat pemindaian maju dan mundur (direction: 'forward'/'backward').
  • dropIndex("nama") menghapus index berdasarkan nama, bukan key pattern.