summaryrefslogtreecommitdiff
path: root/jstests/core/plan_cache_list_plans.js
blob: 11c7922b4b13e08550c3e377b3c273f18b2e9aee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Test the planCacheListPlans command.
//
// @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,
//   does_not_support_stepdowns,
// ]

(function() {
    "use strict";
    let t = db.jstests_plan_cache_list_plans;
    t.drop();

    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;
    }

    // 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');
        let kMillisecondsPerHour = 1000 * 60 * 60;
        assert.lte(Math.abs(date - res.timeOfCreation.getTime()),
                   kMillisecondsPerHour,
                   'timeOfCreation value is incorrect');
    }

    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}));

    // We need two indices so that the MultiPlanRunner is executed.
    assert.commandWorked(t.ensureIndex({a: 1}));
    assert.commandWorked(t.ensureIndex({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');

    // Create a cache entry.
    assert.eq(1,
              t.find({a: 1, b: 1}, {_id: 0, a: 1}).sort({a: -1}).itcount(),
              'unexpected document count');

    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.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]));
    }

    // 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');
    let entryNewShape = getPlansForCacheEntry({a: 123}, {b: -1, a: 1}, {});
    assert.eq(entry.hasOwnProperty("queryHash"), true);
    assert.eq(entryNewShape.hasOwnProperty("queryHash"), true);
    assert.neq(entry["queryHash"], entryNewShape["queryHash"]);
    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}));

    // 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');
    }

    now = (new Date()).getTime();
    checkTimeOfCreation({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1}, now);

    entry = getPlansForCacheEntry({a: 3, b: 3}, {a: -1}, {_id: 0, a: 1});
    assert(entry.hasOwnProperty('works'), 'works missing from planCacheListPlans() result');
    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');
        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(plans[i].reason.stats.hasOwnProperty('stage'), 'no stats inserted for plan ' + i);
    }
})();