summaryrefslogtreecommitdiff
path: root/jstests/core/plan_cache_list_plans.js
diff options
context:
space:
mode:
authorDavid Storch <david.storch@mongodb.com>2019-11-26 14:29:44 +0000
committerevergreen <evergreen@mongodb.com>2019-11-26 14:29:44 +0000
commit36ff260fe666489b8e8021882ee1ec3315526c92 (patch)
tree7de6c2a0da9c3766c56a1a7f4a7dadf141df9203 /jstests/core/plan_cache_list_plans.js
parent03f3b000c26146a8194bebb6124623ac0ebf20ec (diff)
downloadmongo-36ff260fe666489b8e8021882ee1ec3315526c92.tar.gz
SERVER-44701 Remove support for 'planCacheListQueryShapes' and 'planCacheListPlans'.
As an alternative, use the $planCacheStats aggregation stage. This metadata source, when placed first in the pipeline, returns one document per plan cache entry for a particular collection. This data can then be filtered, projected, grouped, or otherwise processed with the full power of MQL.
Diffstat (limited to 'jstests/core/plan_cache_list_plans.js')
-rw-r--r--jstests/core/plan_cache_list_plans.js134
1 files changed, 73 insertions, 61 deletions
diff --git a/jstests/core/plan_cache_list_plans.js b/jstests/core/plan_cache_list_plans.js
index a077f9fafbe..f06ca63dd11 100644
--- a/jstests/core/plan_cache_list_plans.js
+++ b/jstests/core/plan_cache_list_plans.js
@@ -1,84 +1,103 @@
-// Test the planCacheListPlans command.
+// Tests for using $planCacheStats to list cached plans.
//
// @tags: [
// # This test attempts to perform queries and introspect the server's plan cache entries. The
// # former operation may be routed to a secondary in the replica set, whereas the latter must be
// # routed to the primary.
// assumes_read_preference_unchanged,
+// assumes_read_concern_unchanged,
// does_not_support_stepdowns,
// # If the balancer is on and chunks are moved, the plan cache can have entries with isActive:
// # false when the test assumes they are true because the query has already been run many times.
// assumes_balancer_off,
// inspects_whether_plan_cache_entry_is_active,
+// assumes_against_mongod_not_mongos,
// ]
(function() {
"use strict";
-let t = db.jstests_plan_cache_list_plans;
-t.drop();
+let coll = db.jstests_plan_cache_list_plans;
+coll.drop();
+
+function dumpPlanCacheState() {
+ return coll.aggregate([{$planCacheStats: {}}]).toArray();
+}
function getPlansForCacheEntry(query, sort, projection) {
- let key = {query: query, sort: sort, projection: projection};
- let res = t.runCommand('planCacheListPlans', key);
- assert.commandWorked(res, 'planCacheListPlans(' + tojson(key, '', true) + ' failed');
- assert(res.hasOwnProperty('plans'),
- 'plans missing from planCacheListPlans(' + tojson(key, '', true) + ') result');
- return res;
+ const match = {
+ 'createdFromQuery.query': query,
+ 'createdFromQuery.sort': sort,
+ 'createdFromQuery.projection': projection
+ };
+ const res = coll.aggregate([{$planCacheStats: {}}, {$match: match}]).toArray();
+ // We expect exactly one matching cache entry.
+ assert.eq(1, res.length, dumpPlanCacheState());
+ return res[0];
+}
+
+function assertNoCacheEntry(query, sort, projection) {
+ const match = {
+ 'createdFromQuery.query': query,
+ 'createdFromQuery.sort': sort,
+ 'createdFromQuery.projection': projection
+ };
+ assert.eq(0,
+ coll.aggregate([{$planCacheStats: {}}, {$match: match}]).itcount(),
+ dumpPlanCacheState());
}
// Assert that timeOfCreation exists in the cache entry. The difference between the current time
// and the time a plan was cached should not be larger than an hour.
function checkTimeOfCreation(query, sort, projection, date) {
- let key = {query: query, sort: sort, projection: projection};
- let res = t.runCommand('planCacheListPlans', key);
- assert.commandWorked(res, 'planCacheListPlans(' + tojson(key, '', true) + ' failed');
- assert(res.hasOwnProperty('timeOfCreation'), 'timeOfCreation missing from planCacheListPlans');
+ const match = {
+ 'createdFromQuery.query': query,
+ 'createdFromQuery.sort': sort,
+ 'createdFromQuery.projection': projection
+ };
+ const res = coll.aggregate([{$planCacheStats: {}}, {$match: match}]).toArray();
+ // We expect exactly one matching cache entry.
+ assert.eq(1, res.length, res);
+ const cacheEntry = res[0];
+
+ assert(cacheEntry.hasOwnProperty('timeOfCreation'), cacheEntry);
let kMillisecondsPerHour = 1000 * 60 * 60;
- assert.lte(Math.abs(date - res.timeOfCreation.getTime()),
- kMillisecondsPerHour,
- 'timeOfCreation value is incorrect');
+ assert.lte(
+ Math.abs(date - cacheEntry.timeOfCreation.getTime()), kMillisecondsPerHour, cacheEntry);
}
-assert.commandWorked(t.save({a: 1, b: 1}));
-assert.commandWorked(t.save({a: 1, b: 2}));
-assert.commandWorked(t.save({a: 1, b: 2}));
-assert.commandWorked(t.save({a: 2, b: 2}));
+assert.commandWorked(coll.insert({a: 1, b: 1}));
+assert.commandWorked(coll.insert({a: 1, b: 2}));
+assert.commandWorked(coll.insert({a: 1, b: 2}));
+assert.commandWorked(coll.insert({a: 2, b: 2}));
// We need two indices so that the MultiPlanRunner is executed.
-assert.commandWorked(t.ensureIndex({a: 1}));
-assert.commandWorked(t.ensureIndex({a: 1, b: 1}));
+assert.commandWorked(coll.createIndex({a: 1}));
+assert.commandWorked(coll.createIndex({a: 1, b: 1}));
-// Invalid key should be an error.
-assert.eq([],
- getPlansForCacheEntry({unknownfield: 1}, {}, {}).plans,
- 'planCacheListPlans should return empty results on unknown query shape');
+// Check that there are no cache entries associated with an unknown field.
+assertNoCacheEntry({unknownfield: 1}, {}, {});
// Create a cache entry.
-assert.eq(
- 1, t.find({a: 1, b: 1}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'unexpected document count');
+assert.eq(1,
+ coll.find({a: 1, b: 1}, {_id: 0, a: 1}).sort({a: -1}).itcount(),
+ 'unexpected document count');
+// Verify that the time of creation listed for the plan cache entry is reasonably close to 'now'.
let now = (new Date()).getTime();
checkTimeOfCreation({a: 1, b: 1}, {a: -1}, {_id: 0, a: 1}, now);
// Retrieve plans for valid cache entry.
let entry = getPlansForCacheEntry({a: 1, b: 1}, {a: -1}, {_id: 0, a: 1});
-assert(entry.hasOwnProperty('works'),
- 'works missing from planCacheListPlans() result ' + tojson(entry));
+assert(entry.hasOwnProperty('works'), entry);
assert.eq(entry.isActive, false);
-let plans = entry.plans;
-assert.eq(2, plans.length, 'unexpected number of plans cached for query');
-
-// Print every plan.
-// Plan details/feedback verified separately in section after Query Plan Revision tests.
-print('planCacheListPlans result:');
-for (let i = 0; i < plans.length; i++) {
- print('plan ' + i + ': ' + tojson(plans[i]));
-}
+// We expect that there were two candidate plans evaluated when the cache entry was created.
+assert(entry.hasOwnProperty("creationExecStats"), entry);
+assert.eq(2, entry.creationExecStats.length, entry);
// Test the queryHash and planCacheKey property by comparing entries for two different
// query shapes.
-assert.eq(0, t.find({a: 132}).sort({b: -1, a: 1}).itcount(), 'unexpected document count');
+assert.eq(0, coll.find({a: 123}).sort({b: -1, a: 1}).itcount(), 'unexpected document count');
let entryNewShape = getPlansForCacheEntry({a: 123}, {b: -1, a: 1}, {});
assert.eq(entry.hasOwnProperty("queryHash"), true);
assert.eq(entryNewShape.hasOwnProperty("queryHash"), true);
@@ -87,41 +106,34 @@ assert.eq(entry.hasOwnProperty("planCacheKey"), true);
assert.eq(entryNewShape.hasOwnProperty("planCacheKey"), true);
assert.neq(entry["planCacheKey"], entryNewShape["planCacheKey"]);
-//
-// Tests for plan reason and feedback in planCacheListPlans
-//
-
// Generate more plans for test query by adding indexes (compound and sparse). This will also
// clear the plan cache.
-assert.commandWorked(t.ensureIndex({a: -1}, {sparse: true}));
-assert.commandWorked(t.ensureIndex({a: 1, b: 1}));
+assert.commandWorked(coll.createIndex({a: -1}, {sparse: true}));
+assert.commandWorked(coll.createIndex({a: 1, b: 1}));
-// Implementation note: feedback stats is calculated after 20 executions. See
-// PlanCacheEntry::kMaxFeedback.
let numExecutions = 100;
for (let i = 0; i < numExecutions; i++) {
- assert.eq(0, t.find({a: 3, b: 3}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'query failed');
+ assert.eq(0, coll.find({a: 3, b: 3}, {_id: 0, a: 1}).sort({a: -1}).itcount(), 'query failed');
}
+// Verify that the time of creation listed for the plan cache entry is reasonably close to 'now'.
now = (new Date()).getTime();
checkTimeOfCreation({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1}, now);
+// Test that the cache entry is listed as active.
entry = getPlansForCacheEntry({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1});
-assert(entry.hasOwnProperty('works'), 'works missing from planCacheListPlans() result');
+assert(entry.hasOwnProperty('works'), entry);
assert.eq(entry.isActive, true);
-plans = entry.plans;
-// This should be obvious but feedback is available only for the first (winning) plan.
-print('planCacheListPlans result (after adding indexes and completing 20 executions):');
-for (let i = 0; i < plans.length; i++) {
- print('plan ' + i + ': ' + tojson(plans[i]));
- assert.gt(plans[i].reason.score, 0, 'plan ' + i + ' score is invalid');
+// There should be the same number of canidate plan scores as candidate plans.
+assert.eq(entry.creationExecStats.length, entry.candidatePlanScores.length, entry);
+
+// Scores should be greater than zero and sorted descending.
+for (let i = 0; i < entry.candidatePlanScores.length; ++i) {
+ const scores = entry.candidatePlanScores;
+ assert.gt(scores[i], 0, entry);
if (i > 0) {
- assert.lte(plans[i].reason.score,
- plans[i - 1].reason.score,
- 'plans not sorted by score in descending order. ' +
- 'plan ' + i + ' has a score that is greater than that of the previous plan');
+ assert.lte(scores[i], scores[i - 1], entry);
}
- assert(plans[i].reason.stats.hasOwnProperty('stage'), 'no stats inserted for plan ' + i);
}
})();