Understanding the MongoDB Query Planner: A Guide to Efficient Query Optimization

hakimmohamed

Abdelhakim mohamed

Posted on September 19, 2024

Understanding the MongoDB Query Planner: A Guide to Efficient Query Optimization

Analyzing Queries with the MongoDB Query Planner

As developers, we all want our applications to run smoothly—fast and reliable without any hiccups. But when you're working with huge datasets, things can slow down, or worse, crash. That’s where MongoDB’s Query Planner steps in. Think of it as your query’s GPS: it shows you exactly how MongoDB is executing your queries and helps you navigate potential performance bottlenecks.

Let’s walk through how you can use the Query Planner to optimize your queries and avoid the dreaded out-of-memory crashes!


How to Access .explain() in MongoDB Compass

If you’re using MongoDB Compass (which I highly recommend for visual query exploration), analyzing your query is super simple. Here’s how:

  1. Navigate to the Collection: Go to your collection (e.g., products).
  2. Run Your Query: Use the "Filter" section to enter your query.
  3. Click on "Explain": This is your secret weapon! Click the "Explain" tab to see what MongoDB is doing under the hood.
  4. Check the Stats: You’ll now see detailed stats about how MongoDB handled the query.

Here’s a quick look at what that will look like:

Screenshots for reference:

MongoDB Compass Filter

MongoDB Compass Explain


Sample Collection Data

Now that we know how to analyze queries, let’s work with a sample dataset. Imagine you're running an online store, and you have a products collection that looks something like this:

[
  { "_id": 1, "category": "electronics", "name": "Smartphone", "price": 499, "inStock": true },
  { "_id": 2, "category": "electronics", "name": "Laptop", "price": 1299, "inStock": false },
  { "_id": 3, "category": "furniture", "name": "Desk", "price": 250, "inStock": true },
  { "_id": 4, "category": "electronics", "name": "Tablet", "price": 299, "inStock": true },
  { "_id": 5, "category": "furniture", "name": "Chair", "price": 150, "inStock": false }
]
Enter fullscreen mode Exit fullscreen mode

1. Simple Query with Sorting

Let’s start with a basic query. You want to find all products in the electronics category and sort them by price (because who doesn’t want the cheapest tech first, right?).

db.products.find({ category: "electronics" }).sort({ price: 1 }).explain('executionStats');
Enter fullscreen mode Exit fullscreen mode

When you run this query, MongoDB will return your data, but also, with the .explain() method, it will give you a detailed breakdown of how it processed your query.

Sample Output:

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "SORT",
        "sortPattern": { "price": 1 },
        "inputStage": { "stage": "COLLSCAN" }
      }
    }
  },
  "executionStats": {
    "nReturned": 3,
    "totalKeysExamined": 0,
    "totalDocsExamined": 1000,
    "executionTimeMillis": 10
  }
}
Enter fullscreen mode Exit fullscreen mode

Analysis

Uh-oh! Notice the "stage": "COLLSCAN" in the output? That means MongoDB is scanning the entire collection to find your results. This is slow and not ideal if you have a large dataset.

🔧 Fix It! Let’s create an index on price to optimize sorting:

db.products.createIndex({ price: 1 });
Enter fullscreen mode Exit fullscreen mode

2. Compound Index for Better Sorting

Now let’s get even smarter. What if you want to sort by both category and price? Here’s where compound indexes come in. Let’s modify the query and add a compound index for even faster results:

db.products.find({ category: "electronics" }).sort({ price: 1 }).explain('executionStats');
Enter fullscreen mode Exit fullscreen mode

Create the Compound Index:

db.products.createIndex({ category: 1, price: 1 });
Enter fullscreen mode Exit fullscreen mode

Sample Output:

{
  "queryPlanner": {
    "winningPlan": {
      "stage": "FETCH",
      "inputStage": {
        "stage": "IXSCAN",
        "keyPattern": { "category": 1, "price": 1 }
      }
    }
  },
  "executionStats": {
    "nReturned": 3,
    "totalKeysExamined": 50,
    "totalDocsExamined": 3,
    "executionTimeMillis": 2
  }
}
Enter fullscreen mode Exit fullscreen mode

Analysis

Great news! We now have "stage": "IXSCAN" (Index Scan), which is much faster. MongoDB is using the index to quickly find and sort the data without scanning the entire collection. Your application will thank you for the speed boost!


Understanding the Query Planner Output

Now that you’ve seen some examples, let’s break down what MongoDB’s Query Planner is telling you:

  • Winning Plan: This is the plan MongoDB chooses as the most efficient way to execute your query. If it says:

    • COLLSCAN: MongoDB is scanning the entire collection (slow).
    • IXSCAN: MongoDB is using an index (fast and efficient).
    • SORT: Sorting is happening in memory, which might lead to performance issues or even crashes if you’re working with big data.
  • Execution Stats:

    • nReturned: The number of documents returned.
    • totalKeysExamined: The number of index entries MongoDB had to check.
    • totalDocsExamined: The number of documents MongoDB scanned.
    • executionTimeMillis: The total time it took to run the query. You want this to be as low as possible!

Example: Filtering, Sorting, and Pagination

Let’s combine filtering, sorting, and pagination for a more complex example. Here’s a query where we’re looking for in-stock electronics, sorted by price, and limited to the top 5 results:

db.products.find({ category: "electronics", inStock: true }).sort({ price: -1 }).limit(5).explain('executionStats');
Enter fullscreen mode Exit fullscreen mode

Sample Output:

{
  "queryPlanner": {
  "winningPlan": { "stage": "IXSCAN" },
  "executionStats": {
    "nReturned": 5,
    "totalKeysExamined": 20,
    "totalDocsExamined": 5,
    "executionTimeMillis": 3
  }
}
Enter fullscreen mode Exit fullscreen mode

Analysis

  • Stage: The plan is using IXSCAN, meaning MongoDB efficiently leverages the index for filtering and sorting.
  • Stats: The low totalDocsExamined and fast execution time show that this query is well-optimized.

Tips for Query Optimization

Here are a few handy tips to make sure your MongoDB queries stay fast and efficient:

  • 📌 Use Compound Indexes: If you're filtering and sorting on multiple fields, create compound indexes to support both operations.
  • 📌 Limit Data Before Sorting: Always try to reduce the dataset with filters before applying sorting. MongoDB can handle smaller sets much faster.
  • 📌 Paginate for Large Results: Use limit() and skip() to break up large result sets into smaller, more manageable chunks. This keeps memory usage low.

References


Conclusion

Optimizing MongoDB queries doesn’t have to be complicated. By using the Query Planner, creating efficient indexes, and fine-tuning your queries, you can prevent out-of-memory crashes and boost your application’s performance.

Thank you for reading, and happy coding! 🎉

💖 💪 🙅 🚩
hakimmohamed
Abdelhakim mohamed

Posted on September 19, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related