Query paling cepat bukan yang memakai index bagus — melainkan yang tidak membuka document sama sekali. Index menyimpan nilai field ter-index, jadi ketika filter dan projection sebuah query semuanya berada di index, MongoDB cukup membaca entri index dan selesai. Query seperti itu disebut covered query, dan jejaknya sangat khas: totalDocsExamined: 0. Chapter ini membangun query tertutup penuh, lalu menggagalkannya dengan tiga cara berbeda untuk memahami batas-batasnya.

Peta materi Chapter ini:

  • Syarat covered query dan bukti tahap PROJECTION_COVERED.
  • Dua penyabot: _id yang tertinggal dan field non-index di projection.
  • Batasan multikey: field array tidak bisa menutup query.

Query yang Selesai di Index #

Siapkan index compound { judul: 1, harga: 1 }, lalu query yang menyaring judul dan hanya mengembalikan judul serta harga — dua-duanya ada di index:

js
use latihan_index
db.buku.drop()
db.buku.insertMany([
  { judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, kategori: "fiksi", tag: ["fiksi", "sastra"] },
  { judul: "Bumi", penulis: "Tere Liye", harga: 89000, kategori: "fiksi", tag: ["fiksi", "remaja"] },
  { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 89000, kategori: "nonfiksi", tag: ["filsafat"] },
  { judul: "Rumah Kaca", penulis: "Tere Liye", harga: 72000, kategori: "fiksi", tag: ["fiksi", "remaja"] },
  { judul: "Hujan Bulan Juni", penulis: "Sapardi Djoko Damono", harga: 65000, kategori: "puisi", tag: ["puisi", "sastra"] }
])
db.buku.createIndex({ judul: 1, harga: 1 })
function statistik(rencana) {
  const s = rencana.executionStats
  return { tahap: rencana.queryPlanner.winningPlan.stage, hasil: s.nReturned, dokumenDiperiksa: s.totalDocsExamined }
}
printjson(db.buku.find({ judul: "Bumi" }, { _id: 0, judul: 1, harga: 1 }).toArray())
printjson(statistik(db.buku.find({ judul: "Bumi" }, { _id: 0, judul: 1, harga: 1 }).explain("executionStats")))

Output yang Dihasilkan:

text
switched to db latihan_index
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad3bf3ad7c5c965a56cfeba'),
    '1': ObjectId('6ad3bf3ad7c5c965a56cfebb'),
    '2': ObjectId('6ad3bf3ad7c5c965a56cfebc'),
    '3': ObjectId('6ad3bf3ad7c5c965a56cfebd'),
    '4': ObjectId('6ad3bf3ad7c5c965a56cfebe')
  }
}
judul_1_harga_1
[Function: statistik]
[
  {
    judul: 'Bumi',
    harga: 89000
  }
]

{
  tahap: 'PROJECTION_COVERED',
  hasil: 1,
  dokumenDiperiksa: 0
}

Dua tanda tangan covered query tampil sekaligus. Tahap teratas rencana adalah PROJECTION_COVERED — projection dipenuhi langsung dari index — dan dokumenDiperiksa bernilai 0: lima document di collection tidak pernah dibuka, sebab kunci index ("Bumi", 89000) sudah membawa semua yang diminta. Pada collection satu juta baris, selisih antara membaca satu entri index dan membuka document penuh dari disk terasa nyata.

Dua Penyabot Covered Query #

Cukup satu field di luar index — dan yang paling sering terlupa justru _id, yang secara bawaan selalu ikut dikembalikan. Bandingkan dua variasi yang sama-sama gagal ditutup:

js
printjson({
  idTertinggal: statistik(db.buku.find({ judul: "Bumi" }, { judul: 1, harga: 1 }).explain("executionStats")),
  proyeksiPenulis: statistik(db.buku.find({ judul: "Bumi" }, { _id: 0, judul: 1, penulis: 1 }).explain("executionStats"))
})

Output yang Dihasilkan:

text
{
  idTertinggal: {
    tahap: 'PROJECTION_SIMPLE',
    hasil: 1,
    dokumenDiperiksa: 1
  },
  proyeksiPenulis: {
    tahap: 'PROJECTION_SIMPLE',
    hasil: 1,
    dokumenDiperiksa: 1
  }
}

Kedua query kembali membuka document (FETCH, satu document diperiksa). Variasi pertama lupa _id: 0 — field _id tidak ada dalam index { judul: 1, harga: 1 }, sehingga MongoDB harus turun ke document untuk mengambilnya. Variasi kedua meminta penulis yang memang tidak pernah di-index. Aturannya karena itu lugas: covered query menuntut setiap field pada filter dan projection — termasuk _id — berada di dalam index.

Batasan Multikey #

Field array tidak bisa menutup query yang memproyeksikannya. Index multikey (Chapter Multikey Index) menyimpan satu kunci per elemen — sementara tag: ["fiksi", "sastra"] harus dikembalikan utuh sebagai array. Membangun ulang array dari potongan kunci tidak mungkin, jadi document wajib dibuka:

js
db.buku.createIndex({ tag: 1 })
printjson(db.buku.find({ tag: "fiksi" }, { _id: 0, tag: 1 }).toArray())
printjson(statistik(db.buku.find({ tag: "fiksi" }, { _id: 0, tag: 1 }).explain("executionStats")))

Output yang Dihasilkan:

text
tag_1
[
  {
    tag: [
      'fiksi',
      'sastra'
    ]
  },
  {
    tag: [
      'fiksi',
      'remaja'
    ]
  },
  {
    tag: [
      'fiksi',
      'remaja'
    ]
  }
]

{
  tahap: 'PROJECTION_SIMPLE',
  hasil: 3,
  dokumenDiperiksa: 3
}

Query menemukan tiga buku bertag “fiksi”, tetapi tahap teratasnya PROJECTION_SIMPLE — bukan PROJECTION_COVERED — dan tiga document diperiksa. Query tidak tertutup. Yang tetap boleh dari index multikey adalah pemakaiannya sebagai filter: find({ tag: "fiksi" }) tetap efisien lewat IXSCAN. Larangannya spesifik: field array tidak boleh muncul di projection bila query ingin tertutup penuh.

Rangkuman #

  • Covered query terpenuhi seluruhnya dari index: tahap PROJECTION_COVERED, totalDocsExamined: 0.
  • Syaratnya: semua field filter dan projection ada di index, dengan _id: 0 bila _id bukan bagian index.
  • _id yang tertinggal atau satu field non-index di projection cukup untuk mengembalikan tahap FETCH.
  • Field array tidak bisa menutup query yang memproyeksikannya — index multikey menyimpan elemen per elemen, bukan array utuh.