summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/sbe_plan_cache_key_reporting.js
blob: 5c39ec6362e2b00c6c936ddd3da6eadc2c71212b (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/**
 * Confirms that the SBE and classic engines use the correct format to report 'queryHash' and
 * 'planCacheKey' values across a variety of commands and outputs.
 *
 * @tags: [
 *   requires_profiling
 * ]
 */

(function() {
"use strict";

load("jstests/libs/analyze_plan.js");
load("jstests/libs/log.js");
load("jstests/libs/profiler.js");
load("jstests/libs/sbe_util.js");

const conn = MongoRunner.runMongod({});
assert.neq(conn, null, "mongod failed to start");
const db = conn.getDB("plan_cache_key_reporting");
const coll = db.coll;

if (!checkSBEEnabled(db, ["featureFlagSbePlanCache"])) {
    jsTest.log("Skipping test because SBE plan cache is not enabled.");
    MongoRunner.stopMongod(conn);
    return;
}

assert.commandWorked(db.createCollection(coll.getName()));

// Drop and recreates a test collection along with two indexes: {a: 1} and {a: 1, b: 1}.
function setupCollection() {
    assert(coll.drop());
    assert.commandWorked(coll.createIndexes([{a: 1}, {a: 1, b: 1}]));
}

// Runs the given 'testToRun' twice using the SBE and classic engine and returns the result of each
// run as an array with two elements: first for SBE, second for classic.
//
// Calls 'setupCollection' before each run.
function runTestAgainstSbeAndClassicEngines(testToRun) {
    return ["sbe", "classic"].map((engine) => {
        setupCollection();
        assert.commandWorked(db.adminCommand(
            {setParameter: 1, internalQueryEnableSlotBasedExecutionEngine: engine === "sbe"}));
        return testToRun(engine);
    });
}

// Verifies and asserts that the two objects 'sbe' and 'classic', which represent the result of
// execution of some command agains the SBE and classic engine respectively, contain the 'queryHash'
// and 'planCacheKey' attributes, and the values of those attribuets are different.
//
// The purpose of this assertion is to ensure that the SBE and classic engines use the correct
// 'queryHash' and 'planCacheKey' formats, which are different between the two engines.
function assertQueryHashAndPlanCacheKey(sbe, classic) {
    assert.eq(typeof sbe.queryHash, "string", sbe);
    assert.gt(sbe.queryHash.length, 0, sbe);
    assert.eq(typeof sbe.planCacheKey, "string", sbe);
    assert.gt(sbe.planCacheKey.length, 0, sbe);

    assert.eq(typeof classic.queryHash, "string", classic);
    assert.gt(classic.queryHash.length, 0, classic);
    assert.eq(typeof classic.planCacheKey, "string", classic);
    assert.gt(classic.planCacheKey.length, 0, classic);

    assert.neq(sbe.queryHash, classic.queryHash, `sbe=${tojson(sbe)}, classic=${tojson(classic)}`);
    assert.neq(
        sbe.planCacheKey, classic.planCacheKey, `sbe=${tojson(sbe)}, classic=${tojson(classic)}`);
}

// Validate the the 'queryHash' and 'planCacheKey' are correctly reported in the system.profile
// collection.
(function validateProfilerOutput() {
    const [sbe, classic] = runTestAgainstSbeAndClassicEngines(function(engine) {
        // Set db profiling level to collect data for all operations.
        db.setProfilingLevel(2);

        const comment = "validateProfilerOutput-${engine}";
        // We need to run the query twice in order to recover the plan from the cache and report
        // the 'planCacheKey'.
        [...Array(2)].forEach(
            () => assert.eq(0, coll.aggregate([{$match: {a: 1}}], {comment}).itcount()));

        // Disable profiling and find the matching profiler entry for the command above.
        db.setProfilingLevel(0);
        return getLatestProfilerEntry(db, {"command.comment": comment});
    });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.queryExecutionEngine, "sbe", sbe);
    assert.eq(classic.queryExecutionEngine, "classic", classic);

    assertQueryHashAndPlanCacheKey(sbe, classic);
})();

// Validate the the 'queryHash' and 'planCacheKey' are correctly reported in explain output.
(function validateExplainOutput() {
    const [sbe, classic] = runTestAgainstSbeAndClassicEngines(function(engine) {
        return coll.explain().aggregate([{$match: {a: 1}}]);
    });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.explainVersion, "2", sbe);
    assert.eq(classic.explainVersion, "1", classic);

    assertQueryHashAndPlanCacheKey(sbe.queryPlanner, classic.queryPlanner);
})();

// Validate the the 'queryHash' and 'planCacheKey' are correctly reported in $planCacheStats
// output.
(function validatePlanCacheStatsOutput() {
    const [sbe, classic] = runTestAgainstSbeAndClassicEngines(function(engine) {
        const query = {a: 1};
        // Run the test command once. We're not interested whether the cached entry is active or
        // not, but rather if it's present in the cache.
        assert.eq(0, coll.aggregate([{$match: query}]).itcount());

        const planCacheKey = getPlanCacheKeyFromShape({query, collection: coll, db});
        const allPlanCacheEntries =
            coll.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey}}]).toArray();
        assert.eq(allPlanCacheEntries.length, 1, allPlanCacheEntries);
        return allPlanCacheEntries[0];
    });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.version, "2", sbe);
    assert.eq(classic.version, "1", classic);

    assertQueryHashAndPlanCacheKey(sbe, classic);
})();

// Validate the the 'queryHash' and 'planCacheKey' are correctly reported in log output for slow
// queries.
(function validateSlowQueryLogOutput() {
    const [sbe, classic] = runTestAgainstSbeAndClassicEngines(function(engine) {
        // Set all logging parameters: disable profiling and log all operations at the default
        // logLevel.
        assert.commandWorked(db.setProfilingLevel(0, {slowms: -1}));
        assert.commandWorked(db.setLogLevel(0, "command"));

        // Clear the log before running the test, to guarantee that we do not match against any
        // similar tests which may have run previously.
        assert.commandWorked(db.adminCommand({clearLog: "global"}));

        // Run an aggregate command in order to generate a log line.
        const pipeline = [{$match: {a: 10}}];
        const comment = `validateSlowQueryLogOutput-${engine}`;
        assert.eq(0, coll.aggregate(pipeline, {comment}).itcount());

        // Confirm whether the operation was logged or not.
        const logFields = {command: "aggregate", aggregate: coll.getName(), pipeline, comment};
        const globalLog = assert.commandWorked(db.adminCommand({getLog: "global"}));
        return JSON.parse(findMatchingLogLine(globalLog.log, logFields));
    });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.attr.queryExecutionEngine, "sbe", sbe);
    assert.eq(classic.attr.queryExecutionEngine, "classic", classic);

    assertQueryHashAndPlanCacheKey(sbe.attr, classic.attr);
})();

// Validate that a query with pushed down $lookup stage uses classic plan cache key encoding.
(function validateLookupQueryHashMap() {
    const lookupColl = db.lookupColl;
    lookupColl.drop();
    assert.commandWorked(lookupColl.createIndex({b: 1}));
    const [sbe, classic] =
        runTestAgainstSbeAndClassicEngines(
            function(engine) {
                const pipeline = [
                    {
                        $lookup:
                        {
                            from: lookupColl.getName(),
                            localField: "a",
                            foreignField: "b",
                            as: "whatever"
                        }
                    }
                ];
                return coll.explain().aggregate(pipeline);
            });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.explainVersion, "2", sbe);
    assert.eq(classic.explainVersion, "1", classic);

    // The query hashes and the plan cache keys ('the keys') are different now because
    // 'internalQueryEnableSlotBasedExecutionEngine' flag is encoded into query shape, once this
    // flag is removed  from the query shape encoding the keys will be the same until SERVER-61507
    // is completed, then the keys will be different forever.
    assertQueryHashAndPlanCacheKey(sbe.queryPlanner, classic.stages[0]["$cursor"].queryPlanner);
})();

// Validate that a query with pushed down $group stage uses classic plan cache key encoding.
(function validateGroupQueryHashMap() {
    const groupColl = db.groupColl;
    groupColl.drop();
    assert.commandWorked(groupColl.insertOne({b: 1}));
    const [sbe, classic] = runTestAgainstSbeAndClassicEngines(function(engine) {
        const pipeline = [{
            $group: {
                _id: "$b",
            }
        }];
        return groupColl.explain().aggregate(pipeline);
    });

    assert.neq(sbe, null);
    assert.neq(classic, null);
    assert.eq(sbe.explainVersion, "2", sbe);
    assert.eq(classic.explainVersion, "1", classic);

    // The query hashes and the plan cache keys ('the keys') are different now because
    // 'internalQueryEnableSlotBasedExecutionEngine' flag is encoded into query shape, once this
    // flag is removed from the query shape encoding the keys will be the same until SERVER-61507
    // is completed, then the keys will be different forever.
    assertQueryHashAndPlanCacheKey(sbe.queryPlanner, classic.stages[0]["$cursor"].queryPlanner);
})();

MongoRunner.stopMongod(conn);
}());