diff options
author | David Storch <david.storch@mongodb.com> | 2019-11-26 14:29:44 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-11-26 14:29:44 +0000 |
commit | 36ff260fe666489b8e8021882ee1ec3315526c92 (patch) | |
tree | 7de6c2a0da9c3766c56a1a7f4a7dadf141df9203 /jstests/core/plan_cache_list_plans.js | |
parent | 03f3b000c26146a8194bebb6124623ac0ebf20ec (diff) | |
download | mongo-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.js | 134 |
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); } })(); |