Aggregate query for drawing temporal statistic I used to write simple Mongo queries, but this time I had to create an aggregate query. The purpose is to draw temporal statistic on the main page of my web app.

I'm not confident about his performance in production environment, so I'm looking for advice and review about this one.

db.runs.aggregate([
    {$project: {
        "month": {
                $cond: [
                    {$ifNull: ["$startTime", false]},
                    {$month:"$startTime"},
                    -1
                ]   
            },
        "year": {
                $cond: [
                    {$ifNull: ["$startTime", false]},
                    {$year:"$startTime"},
                    -1
                ]
            },
        "nbItem": 1,
        "cleanUp.nb": 1,
        "startTime": 1
    }},
    {$match: {
        "startTime" : {$ne:null}
    }},
    {$group: {
        "_id": {
            "month":"$month",
            "year":"$year"
        },
        "nbRun":{$sum:1},
        "sumItem":{$sum:"$nbItem"},
        "sumInserted":{$sum:"$cleanUp.nb"}
    }}
])


As expected the result looks like:

{
    "_id" : {
        "month" : 2,
        "year" : 2017
    },
    "nbRun" : 5,
    "sumItem" : 567372,
    "sumInserted" : 35107
}
{
    "_id" : {
        "month" : 10,
        "year" : 2016
    },
    "nbRun" : 3,
    "sumItem" : 677722,
    "sumInserted" : 16090
}
...
Here your doing something very redondant : 

if you move the $match stage at the begining of the aggregation, you no longer need to check wether startTime is null in your project stage: 

Plus, a $match stage should always be at the beginning of you aggregation pipeline, as it will reduce the number of documents treated in the next stage. 
from MongoDB documentation: 


  Pipeline Optimization
  
  Place the $match as early in the aggregation pipeline as possible.
  Because $match limits the total number of documents in the aggregation
  pipeline, earlier $match operations minimize the amount of processing
  down the pipe.
  
  If you place a $match at the very beginning of a
  pipeline, the query can take advantage of indexes like any other
  db.collection.find() or db.collection.findOne().


so your query becomes: 

db.runs.aggregate([
    {$match: {
        "startTime" : {$ne:null}
    }},
    {$project: {
        "month": {$month:"$startTime"},
        "year": {$year:"$startTime"},
        "nbItem": 1,
        "cleanUp.nb": 1,
        "startTime": 1
    }},
    {$group: {
        "_id": {
            "month":"$month",
            "year":"$year"
        },
        "nbRun": {$sum:1},
        "sumItem": {$sum:"$nbItem"},
        "sumInserted": {$sum:"$cleanUp.nb"}
    }}
])


Now we can push it even further by merging the $project and the $group stage into a single $group stage like this : 

db.runs.aggregate([
    {$match: {
        "startTime" : {$ne:null}
    }},
    {$group: {
        "_id": {
            "month": {$month: "$startTime"},
            "year": {$year: "$startTime"}
        },
        "nbRun": {$sum:1},
        "sumItem": {$sum:"$nbItem"},
        "sumInserted": {$sum:"$cleanUp.nb"}
    }}
])


The query should run faster and is now way more readable. 

Finally, you can look at the explain() of the query to understand exactly what happens. Make sure that the startTime field is properly indexed. 

If the query still does not run fast enough, consider adding the field year and month directly in your documents to get rid of the $year and $month operators