|
We should extend aggregation explain to report the appropriate information in "executionStats" and "allPlansExecution" explain modes. In "executionStats" mode, the $cursor stage will report detailed information in the same format as used for the explaining other supported commands (find, count, etc.):
> db.c.drop();
|
> for (let i = 0; i < 10; i++) { db.c.insert({_id: i, a: 1, b: i}); }
|
> db.c.createIndex({a: 1});
|
> db.c.createIndex({b: 1});
|
|
> db.c.explain("executionStats").aggregate([{$match: {a: 1, b: {$gt: 3}}}]);
|
{
|
"stages" : [
|
{
|
"$cursor" : {
|
"query" : {
|
"a" : 1,
|
"b" : {
|
"$gt" : 3
|
}
|
},
|
"queryPlanner" : {
|
"plannerVersion" : 1,
|
"namespace" : "test.c",
|
"indexFilterSet" : false,
|
"parsedQuery" : {
|
"$and" : [
|
{
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
{
|
"b" : {
|
"$gt" : 3
|
}
|
}
|
]
|
},
|
"winningPlan" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"keyPattern" : {
|
"b" : 1
|
},
|
"indexName" : "b_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"b" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"b" : [
|
"(3.0, inf.0]"
|
]
|
}
|
}
|
},
|
"rejectedPlans" : [
|
{
|
"stage" : "FETCH",
|
"filter" : {
|
"b" : {
|
"$gt" : 3
|
}
|
},
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"keyPattern" : {
|
"a" : 1
|
},
|
"indexName" : "a_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"a" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"a" : [
|
"[1.0, 1.0]"
|
]
|
}
|
}
|
}
|
]
|
},
|
"executionStats" : {
|
"executionSuccess" : true,
|
"nReturned" : 6,
|
"executionTimeMillis" : 1,
|
"totalKeysExamined" : 6,
|
"totalDocsExamined" : 6,
|
"executionStages" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 8,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"docsExamined" : 6,
|
"alreadyHasObj" : 0,
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"keyPattern" : {
|
"b" : 1
|
},
|
"indexName" : "b_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"b" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"b" : [
|
"(3.0, inf.0]"
|
]
|
},
|
"keysExamined" : 6,
|
"seeks" : 1,
|
"dupsTested" : 0,
|
"dupsDropped" : 0,
|
"seenInvalidated" : 0
|
}
|
}
|
}
|
}
|
}
|
],
|
"ok" : 1,
|
"operationTime" : Timestamp(0, 0)
|
}
|
The stages[0].$cursor.executionStats section is entirely new. In "allPlansExecution" mode, the executionStats section is extended with information on all candidate plans evaluated by the plan ranker:
> db.c.explain("allPlansExecution").aggregate([{$match: {a: 1, b: {$gt: 3}}}])
|
{
|
> db.c.explain("allPlansExecution").aggregate([{$match: {a: 1, b: {$gt: 3}}}])
|
{
|
"stages" : [
|
{
|
"$cursor" : {
|
"query" : {
|
"a" : 1,
|
"b" : {
|
"$gt" : 3
|
}
|
},
|
"queryPlanner" : {
|
"plannerVersion" : 1,
|
"namespace" : "test.c",
|
"indexFilterSet" : false,
|
"parsedQuery" : {
|
"$and" : [
|
{
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
{
|
"b" : {
|
"$gt" : 3
|
}
|
}
|
]
|
},
|
"winningPlan" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"keyPattern" : {
|
"b" : 1
|
},
|
"indexName" : "b_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"b" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"b" : [
|
"(3.0, inf.0]"
|
]
|
}
|
}
|
},
|
"rejectedPlans" : [
|
{
|
"stage" : "FETCH",
|
"filter" : {
|
"b" : {
|
"$gt" : 3
|
}
|
},
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"keyPattern" : {
|
"a" : 1
|
},
|
"indexName" : "a_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"a" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"a" : [
|
"[1.0, 1.0]"
|
]
|
}
|
}
|
}
|
]
|
},
|
"executionStats" : {
|
"executionSuccess" : true,
|
"nReturned" : 6,
|
"executionTimeMillis" : 0,
|
"totalKeysExamined" : 6,
|
"totalDocsExamined" : 6,
|
"executionStages" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 8,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"docsExamined" : 6,
|
"alreadyHasObj" : 0,
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"keyPattern" : {
|
"b" : 1
|
},
|
"indexName" : "b_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"b" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"b" : [
|
"(3.0, inf.0]"
|
]
|
},
|
"keysExamined" : 6,
|
"seeks" : 1,
|
"dupsTested" : 0,
|
"dupsDropped" : 0,
|
"seenInvalidated" : 0
|
}
|
},
|
"allPlansExecution" : [
|
{
|
"nReturned" : 3,
|
"executionTimeMillisEstimate" : 0,
|
"totalKeysExamined" : 7,
|
"totalDocsExamined" : 7,
|
"executionStages" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"b" : {
|
"$gt" : 3
|
}
|
},
|
"nReturned" : 3,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 3,
|
"needTime" : 4,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 0,
|
"invalidates" : 0,
|
"docsExamined" : 7,
|
"alreadyHasObj" : 0,
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"nReturned" : 7,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 7,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 0,
|
"invalidates" : 0,
|
"keyPattern" : {
|
"a" : 1
|
},
|
"indexName" : "a_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"a" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"a" : [
|
"[1.0, 1.0]"
|
]
|
},
|
"keysExamined" : 7,
|
"seeks" : 1,
|
"dupsTested" : 0,
|
"dupsDropped" : 0,
|
"seenInvalidated" : 0
|
}
|
}
|
},
|
{
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"totalKeysExamined" : 6,
|
"totalDocsExamined" : 6,
|
"executionStages" : {
|
"stage" : "FETCH",
|
"filter" : {
|
"a" : {
|
"$eq" : 1
|
}
|
},
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"docsExamined" : 6,
|
"alreadyHasObj" : 0,
|
"inputStage" : {
|
"stage" : "IXSCAN",
|
"nReturned" : 6,
|
"executionTimeMillisEstimate" : 0,
|
"works" : 7,
|
"advanced" : 6,
|
"needTime" : 0,
|
"needYield" : 0,
|
"saveState" : 1,
|
"restoreState" : 1,
|
"isEOF" : 1,
|
"invalidates" : 0,
|
"keyPattern" : {
|
"b" : 1
|
},
|
"indexName" : "b_1",
|
"isMultiKey" : false,
|
"multiKeyPaths" : {
|
"b" : [ ]
|
},
|
"isUnique" : false,
|
"isSparse" : false,
|
"isPartial" : false,
|
"indexVersion" : 2,
|
"direction" : "forward",
|
"indexBounds" : {
|
"b" : [
|
"(3.0, inf.0]"
|
]
|
},
|
"keysExamined" : 6,
|
"seeks" : 1,
|
"dupsTested" : 0,
|
"dupsDropped" : 0,
|
"seenInvalidated" : 0
|
}
|
}
|
}
|
]
|
}
|
}
|
}
|
],
|
"ok" : 1,
|
"operationTime" : Timestamp(0, 0)
|
}
|
Here, the stages[0].$cursor.executionStats.allPlansExecution section is entirely new. This is implemented by allowing the explain command to accept a nested aggregate command:
{explain: {aggregate: ...}, verbosity: "allPlansExecution"}
|
The aggregate command's explain parameter is still supported, as in {aggregate: "myCollection", pipeline: [], explain: true}. However, the explain command format is preferred. It is illegal to combine the two formats, as in
{explain: {aggregate: "myCollection", pipeline: [], explain: true}, verbosity: "allPlansExecution"}
|
Finally, since the explain command has never supported readConcern or writeConcern, we have made it an error to use readConcern or writeConcern in combination with the aggregate command explain flag. All of the following examples are illegal:
{aggregate: "myCollection", pipeline: [{$out: "foo"}], explain: true, writeConcern: {w: 1}}
|
{explain: {aggregate: "myCollection", pipeline: [{$out: "foo"}]}, writeConcern: {w: 1}}
|
{aggregate: "myCollection", pipeline: [{$out: "foo"}], explain: true, readConcern: {level: "majority"}}
|
{explain: {aggregate: "myCollection", pipeline: [{$out: "foo"}]}, readConcern: {level: "majority"}}
|
This is a minor breaking change in 3.6 which will have to be accommodated by drivers.
Note that aggregation stages other than $cursor do not currently report execution stats. Implementing this is further work tracked by SERVER-21784.
|