explain, it is found that the originally used efficient index has been replaced by another inefficient one. This phenomenon, where the execution plan switches repeatedly between different indexes and query latency fluctuates significantly, is referred to as index drift (Plan Drift). The key characteristics of drift are:explain shows that the index currently in use differs from the historical one.orders has two indexes:{ status: 1 }{ uid: 1, createTime: -1 }find({ status, uid }).sort({ createTime: -1 }), which has a consistent query pattern, performance can vary significantly when parameters differ:Parameter | status Percentage in Full Table | More Suitable Index |
status="cancelled" | 0.1% | { status: 1 } (The proportion of cancelled orders is low, and scanning by the status index incurs a lower cost.) |
status="paid" | 80% | { uid: 1, createTime: -1 } (Scanning by status requires traversing most of the data, whereas scanning by uid enables precise targeting.) |
pending and gradually shift to being predominantly paid as the business develops.mongod processdb.collection.getPlanCache().clear()Method | Scope | Cluster-Level Effective | Persistent After Restart | Applicable Scenarios |
Add hint to commands. | Single Query | Yes | Yes | Temporary troubleshooting |
Embed hint in application code. | Business-specified Queries | Yes | Yes (with code) | Critical business path |
planCacheSetFilter (Deprecated) | All Queries of the Same Pattern | No (set per node) | Yes | Not recommended |
setQuerySettings (Recommended from version 8.0) | All Queries of the Same Pattern | Yes | Yes | Recommended solution |
planCacheSetFilter (Index Filter) has been officially marked as deprecated. This command has two inherent limitations: its settings are lost after a restart and must be configured individually on each node, leading to high operational costs in cluster environments. The official recommendation is to use setQuerySettings instead. This command supports cluster-wide application and persistence across restarts, making it suitable for both replica sets and sharded clusters.hint to Commandsdb.orders.find({ uid: 1001, status: "paid" }).hint({ uid: 1, createTime: -1 }).sort({ createTime: -1 })
hint in Application Code# Python Driver Examplecursor = collection.find({"uid": uid, "status": status},hint=[("uid", 1), ("createTime", -1)],).sort([("createTime", -1)])
setQuerySettings (Recommended from 8.0)setQuerySettings is executed once, the settings are applied to matching query structures across the entire cluster (applicable to both replica sets and sharded clusters) and persist after a restart:db.adminCommand({setQuerySettings: {find: "orders",filter: { uid: 1, status: "paid" },sort: { createTime: -1 },$db: "shop"},settings: {indexHints: [{ns: { db: "shop", coll: "orders" },allowedIndexes: [ { uid: 1, createTime: -1 } ]}]}})
db.aggregate([{ $querySettings: {} }])
db.adminCommand({removeQuerySettings: {find: "orders",filter: { uid: 1, status: "paid" },sort: { createTime: -1 },$db: "shop"}})
Scenario | Recommended Solution |
Persistence requiring cluster-level, long-term persistence for version 8.0 and above. | setQuerySettings (Recommended solution) |
Critical transaction path requiring management with code versions. | Embed hint in application code. |
Temporary troubleshooting and canary testing | Add hint to commands. |
Non-critical queries that tolerate occasional plan drifts. | Do not fix plans, retain adaptive capability. |
// Classic Engine replan Countdb.serverStatus().metrics.query.planCache.classic.replanned// SBE Engine replan Countdb.serverStatus().metrics.query.planCache.sbe.replanned
"replanReason": "cached plan was less efficient than expected: ..."
explain("allPlansExecution") to examine the execution of all candidate plans. Compare executionTimeMillis, totalKeysExamined, and totalDocsExamined, and incorporate representative business parameter values to identify the index that performs stably in the primary business scenarios. This index is then designated as the pinning target.setQuerySettings as an example:db.adminCommand({setQuerySettings: {find: "orders",filter: { uid: 1, status: "paid" },sort: { createTime: -1 },$db: "shop"},settings: {indexHints: [{ns: { db: "shop", coll: "orders" },allowedIndexes: [ { uid: 1, createTime: -1 } ]}]}})
db.orders.find({ uid: 1001, status: "paid" }).sort({ createTime: -1 }).explain("queryPlanner")
queryPlanner.querySettings: Displays the applied settings.queryPlanner.winningPlan: The index used is the target index.metrics.query.planCache.classic.replanned and metrics.query.planCache.sbe.replanned: The overall growth trend should decline. Furthermore, confirm by using the slow log query log that this type of query no longer frequently triggers replan.// 1. Collect the performance of all current candidate plans to identify the index that is more suitable under new data.db.orders.find({ uid: 1001, status: "paid" }).sort({ createTime: -1 }).explain("allPlansExecution")// 2. Remove the query settings.db.adminCommand({removeQuerySettings: {find: "orders",filter: { uid: 1, status: "paid" },sort: { createTime: -1 },$db: "shop"}})// 3. Clear the corresponding Plan Cache to enable MongoDB to re-evaluate and select an execution plan based on the new data distribution.db.orders.getPlanCache().clear()
Focus | Conclusion |
Root Cause of Plan Drift | Plan Cache caches queries by their "query shape", but the optimal index for different parameters under the same shape may differ. |
Peak Occurrence Period | Within a period after events such as instance restart, index addition, or index deletion. |
Detection Method | Locate via metrics.query.planCache.classic.replanned, metrics.query.planCache.sbe.replanned, and replanReason in logs. |
Recommended Solution for MongoDB 8.0 | setQuerySettings: Cluster-level effect, persists after restart, applicable to replica sets and sharded clusters. |
Implementation Principles | Only fix plans for critical business queries, retain adaptive capability for non-critical queries. |
planCacheShapeHash, which can be used to understand why queries of the same shape reuse execution plans.queryPlanner, winningPlan, executionStats, totalKeysExamined, and totalDocsExamined, which can be used to troubleshoot slow log queries and index usage.explain and slow log query logs.indexHints, queryFramework, and reject.피드백