enes

Enes Altınkaya

How to Explain a Mongodb Query in Spring

How to Explain a Mongodb Query in Spring

Update 2020-08-08

modifiers can not be used anymore.

Following method works as of August 2020.

  • Spring Boot version: 2.3.1
  • MongoDB Java Driver Version: 4.0.4

Using explain db command.

More on that https://docs.mongodb.com/manual/reference/command/explain/#dbcmd.explain

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;

@Service
public class MongodbExplain {

    @Autowired
    private MongoTemplate mongoTemplate;

    public String explainQuery(Query query, String collectionName) {
        Document explainDocument = new Document();
        explainDocument.put("find", collectionName);
        explainDocument.put("filter", query.getQueryObject());

        Document command = new Document();
        command.put("explain", explainDocument);

        Document explainResult = mongoTemplate.getDb().runCommand(command);
        return explainResult.toJson();
    }

    @PostConstruct
    public void postConstrucut() {
        Query query = new Query(Criteria.where("name").is("enes"));
        String explainResult = explainQuery(query, "person");
        System.out.println(explainResult);
    }
    
}

Update 2019-03-23

Guys at MongoDB did some breaking changes to java driver.

Following method works as of March 2019.

modifiers method seems to be deprecated as well. I'll update this page again if they break it too.

  • Spring Boot version: 2.1.3
  • MongoDB Java Driver Version: 3.8.2
package com.example.demo;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
public class MongodbExplain {

    @Autowired
    private MongoTemplate mongoTemplate;

    public String explainQuery(Query query, String collectionName) {
        MongoCollection<Document> collection = mongoTemplate.getCollection(collectionName);
        FindIterable<Document> result = collection.find(query.getQueryObject()).modifiers(new Document("$explain", true));
        return result.first().toJson();
    }

    @PostConstruct
    public void postConstrucut() {
        Query query = new Query(Criteria.where("name").is("enes"));
        String explainResult = explainQuery(query, "person");
        System.out.println(explainResult);
    }
}

For previous versions

Sometimes you want to see if your queries are using indexes or not when using MongoDB with the help of Spring.

With 3 lines of code you can check if an index is being used.

DBCollection collection = mongoTemplate.getCollection(collectionName);
DBCursor cursor = collection.find(query.getQueryObject());
System.out.println(cursor.explain());

Here is a working example.

package enesaltinkayacom.util;

import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import enesaltinkayacom.post.Post;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class Explain {

    @Autowired
    private MongoTemplate mongoTemplate;

    public List<Post> someJob(String title) {
        Criteria criteria = new Criteria();
        criteria.andOperator(Criteria.where("title").is(title),
                Criteria.where("deleted").is(false));
        Query query = new Query(criteria);
        query.limit(10);
        query.skip(10);
        query.with(new Sort(Sort.Direction.DESC, "date"));

        explainQuery(query, "post");

        return mongoTemplate.find(query, Post.class);
    }

    public void explainQuery(Query query, String collectionName) {
        DBCollection collection = mongoTemplate.getCollection(collectionName);
        DBCursor cursor = collection.find(query.getQueryObject());
        System.out.println(cursor.explain());
    }

}

Output is rather long.
I copy and paste such long json outputs to http://jsoneditoronline.org/

You can see that queryPlanner -> winningPlan -> inputStage -> stage is IXSCAN.
Which means our query is indeed using an index.

Stage can be one of the four,

  • COLLSCAN for a collection scan.
  • IXSCAN for scanning index keys.
  • FETCH for retrieving documents.
  • SHARD_MERGE for merging results from shards.

More on that mongodb.com explain-results




























 































































































{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "enesaltinkaya.post",
    "indexFilterSet": false,
    "parsedQuery": {
      "$and": [
        {
          "deleted": {
            "$eq": false
          }
        },
        {
          "title": {
            "$eq": "Test"
          }
        }
      ]
    },
    "winningPlan": {
      "stage": "FETCH",
      "filter": {
        "deleted": {
          "$eq": false
        }
      },
      "inputStage": {
        "stage": "IXSCAN",
        "keyPattern": {
          "title": 1
        },
        "indexName": "title",
        "isMultiKey": false,
        "multiKeyPaths": {
          "title": []
        },
        "isUnique": false,
        "isSparse": false,
        "isPartial": false,
        "indexVersion": 2,
        "direction": "forward",
        "indexBounds": {
          "title": [
            "[\"Test\", \"Test\"]"
          ]
        }
      }
    },
    "rejectedPlans": []
  },
  "executionStats": {
    "executionSuccess": true,
    "nReturned": 0,
    "executionTimeMillis": 0,
    "totalKeysExamined": 0,
    "totalDocsExamined": 0,
    "executionStages": {
      "stage": "FETCH",
      "filter": {
        "deleted": {
          "$eq": false
        }
      },
      "nReturned": 0,
      "executionTimeMillisEstimate": 0,
      "works": 1,
      "advanced": 0,
      "needTime": 0,
      "needYield": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "docsExamined": 0,
      "alreadyHasObj": 0,
      "inputStage": {
        **"stage": "IXSCAN",**
        "nReturned": 0,
        "executionTimeMillisEstimate": 0,
        "works": 1,
        "advanced": 0,
        "needTime": 0,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "invalidates": 0,
        "keyPattern": {
          "title": 1
        },
        "indexName": "title",
        "isMultiKey": false,
        "multiKeyPaths": {
          "title": []
        },
        "isUnique": false,
        "isSparse": false,
        "isPartial": false,
        "indexVersion": 2,
        "direction": "forward",
        "indexBounds": {
          "title": [
            "[\"Test\", \"Test\"]"
          ]
        },
        "keysExamined": 0,
        "seeks": 1,
        "dupsTested": 0,
        "dupsDropped": 0,
        "seenInvalidated": 0
      }
    },
    "allPlansExecution": []
  },
  "serverInfo": {
    "host": "arch",
    "port": 27017,
    "version": "3.4.7",
    "gitVersion": "cf38c1b8a0a8dca4a11737581beafef4fe120bcd"
  },
  "ok": 1
}
Share on