Topik ini menjelaskan cara kerja query planner MongoDB, bagaimana rencana kueri di-cache dan dievaluasi, serta penyebab terjadinya replanning kueri. Topik ini juga mencakup cara mengidentifikasi masalah replanning dan solusi yang tersedia.
Query planner
Untuk kueri tertentu, query planner MongoDB memilih dan menyimpan cache rencana kueri yang paling efisien berdasarkan indeks yang tersedia. Gambar berikut menunjukkan cara kerja query planner.
Evaluasi rencana kueri yang paling efisien didasarkan pada jumlah unit kerja (works) yang dilakukan oleh rencana eksekusi kueri saat query planner mengevaluasi rencana kandidat. Entri cache rencana dapat digunakan kembali oleh kueri berikutnya yang memiliki bentuk kueri (query shape) yang sama.
Status entri cache rencana
Entri cache rencana dapat berada dalam salah satu status berikut:
| State | Description |
|---|---|
| Missing | Tidak ada entri dalam cache rencana. Query planner harus mengevaluasi rencana kandidat dari awal. |
| Inactive | Terdapat entri dalam cache rencana dengan nilai works yang direkam dari evaluasi sebelumnya. Entri ini dapat berpindah ke status Active. |
| Active | Terdapat entri dalam cache rencana dan digunakan untuk kueri yang sesuai. Rencana pemenang dalam status Active dapat kembali ke status Inactive jika tidak lagi efisien. |
Perilaku cache rencana
Cache rencana disimpan sepenuhnya dalam memori dan tidak bertahan setelah restart. Cache ini dihapus dalam situasi berikut:
-
Database MongoDB di-restart.
-
Koleksi atau indeks dihapus.
Cache rencana juga memiliki batas ukuran dan mengikuti mekanisme penggantian cache least recently used (LRU). Entri yang lebih jarang diakses akan dikeluarkan dari cache seiring waktu.
Perintah manajemen cache rencana
Anda dapat menjalankan perintah berikut untuk mengelola entri cache rencana untuk koleksi tertentu:
| Command | Description |
|---|---|
db.<collection>.getPlanCache().clear() |
Menghapus cache rencana untuk suatu koleksi. |
db.<collection>.getPlanCache().listQueryShapes() |
Menampilkan semua bentuk kueri dalam cache rencana untuk suatu koleksi. |
db.<collection>.getPlanCache().getPlansByQuery(...) |
Mengambil rencana kueri yang di-cache untuk kueri tertentu. |
Contoh pengambilan rencana kueri yang di-cache:
db.<collection>.getPlanCache().getPlansByQuery({"query": {"name": "testname"}, "sort": { "name": 1 })
queryHash dan planCacheKey
MongoDB 4.2 dan versi yang lebih baru memperkenalkan queryHash untuk mengidentifikasi bentuk kueri. Setiap bentuk kueri dikaitkan dengan nilai queryHash. MongoDB 4.2 juga memperkenalkan planCacheKey, yang berbeda dari queryHash karena merupakan fungsi dari bentuk kueri dan indeks yang tersedia untuk bentuk tersebut. Jika indeks yang mendukung bentuk kueri dibuat atau dihapus, nilai planCacheKey dapat berubah, tetapi nilai queryHash tetap tidak berubah.
Contoh
Pertimbangkan koleksi dengan indeks dan bentuk kueri berikut:
Indeks:
db.foo.createIndex( { x: 1 } )
db.foo.createIndex( { x: 1, y: 1 } )
db.foo.createIndex( { x: 1, z: 1 }, { partialFilterExpression: { x: { $gt: 10 } } } )
Bentuk kueri:
db.foo.explain().find( { x: { $gt: 5 } } ) // Operasi kueri 1
db.foo.explain().find( { x: { $gt: 20 } } ) // Operasi kueri 2
Indeks ketiga menggunakan ekspresi filter parsial yang mensyaratkan x > 10. Oleh karena itu, indeks ini dapat mendukung operasi kueri 2 (x > 20) tetapi tidak operasi kueri 1 (x > 5). Akibatnya, kedua kueri memiliki nilai planCacheKey yang berbeda. Jika indeks baru {x:1, a:1} dibuat, nilai planCacheKey untuk kedua operasi kueri akan berubah.
Replanning kueri
Ketika data dalam koleksi berubah secara signifikan, rencana kueri yang sebelumnya di-cache mungkin tidak lagi optimal. Query planner mendeteksi hal ini dan mengganti rencana tersebut untuk mempertahankan efisiensi kueri.
Saat Anda menjalankan kueri yang sesuai dengan bentuk kueri dari rencana yang di-cache, query planner langsung menggunakan rencana yang di-cache tanpa mengevaluasi ulang rencana kandidat. Namun, query planner terus memantau efisiensi eksekusi rencana yang di-cache. Jika rencana yang di-cache memerlukan lebih dari 10 kali jumlah unit kerja yang diharapkan, query planner akan menghapus rencana yang di-cache tersebut dan mengevaluasi ulang semua rencana kandidat. Proses ini disebut replanning kueri.
Dampak
-
Replanning kueri yang sering dapat menurunkan performa kueri.
-
Replanning kueri yang berlebihan dapat menyebabkan contention pada mutex lock, sehingga mengakibatkan tingginya utilisasi CPU.
Mengidentifikasi replanning
Anda mungkin menemukan kata kunci "replanned":true dalam log kueri lambat. Kata kunci ini menunjukkan bahwa query planner tidak dapat mempertahankan rencana yang konsisten efisien untuk bentuk kueri tertentu.
Contoh entri log kueri lambat:
"replanned":true,"replanReason":"cached plan was less efficient than expected: expected trial execution to take X works but it took at least 10X works"
Solusi
Solusi berikut diurutkan dari perbaikan jangka panjang yang direkomendasikan hingga mitigasi segera dan langkah darurat terakhir.
Direkomendasikan: optimalkan kueri dan indeks
Optimalkan pernyataan kueri Anda dan buat indeks yang efektif untuk mencegah replanning kueri. Tinjau dan sesuaikan kueri, indeks yang tersedia, serta pola dokumen Anda alih-alih mengandalkan hint atau filter indeks.
Ini adalah pendekatan yang disarankan karena mengatasi akar penyebab replanning, bukan hanya mengatasinya secara sementara.
Direkomendasikan: upgrade versi MongoDB
Jika versi utama instans Anda adalah MongoDB 4.2 atau MongoDB 4.4, perbarui versi minor ke versi terbaru yang tersedia. Hal ini dapat secara signifikan mengurangi contention pada mutex lock. Anda juga dapat melakukan upgrade versi utama ke 5.0 atau 6.0 untuk mengatasi masalah ini. Untuk informasi lebih lanjut mengenai perbaikan kernel terkait, lihat SERVER-40805.
Untuk petunjuk upgrade, lihat Update the minor version of an instance dan Upgrade the major version of an instance.
Hapus cache rencana
Hapus cache rencana dan biarkan query planner memilih rencana kueri yang lebih sesuai untuk kueri Anda.
Gunakan fungsi hint()
Gunakan fungsi hint() dalam kode aplikasi Anda untuk menentukan indeks mana yang harus digunakan oleh query planner untuk kueri yang mengalami replanning. Contoh:
db.<collection>.find({a:"ABC"},{b:1,_id:0}).sort({c:1}).hint({ a:1, c:1, b:1} )
Ketika hint ditentukan untuk suatu kueri, rencana kueri yang dipilih oleh query planner tidak berlaku. Kueri akan menggunakan indeks yang ditentukan dalam hint tersebut.
Gunakan filter indeks
Gunakan filter indeks untuk membatasi indeks mana saja yang dapat dipertimbangkan oleh query planner untuk kueri yang mengalami replanning dalam kode aplikasi Anda. Contoh:
// Setel filter indeks.
db.runCommand(
{
planCacheSetFilter: "<collection>",
query: { a: "ABC" },
projection: { b: 1, _id: 0 },
sort: { c: 1 },
indexes: [
{ a: 1, c: 1 , b: 1 }
]
}
)
// Hapus filter indeks yang ada.
db.runCommand(
{
planCacheClearFilters: "<collection>"
}
)
-
Filter indeks mengesampingkan perilaku query planner yang diharapkan saat memilih rencana kueri.
-
Jika Anda menentukan hint dan filter indeks untuk suatu kueri, filter indeks akan mengambil prioritas atas hint. Gunakan filter indeks dengan hati-hati. Untuk informasi lebih lanjut, lihat Index Filters.
Upgrade spesifikasi instans
Sebagai langkah sementara, upgrade spesifikasi instans untuk mengurangi beban database. Untuk informasi lebih lanjut, lihat Change the configurations of an instance.
Hubungi dukungan teknis
Jika masalah tetap berlanjut setelah Anda mencoba solusi di atas, Submit a ticket untuk menghubungi dukungan teknis.