DEV Community

Dmitry Romanoff
Dmitry Romanoff

Posted on

Playing with Indexes in MongoDB

Indexes in MongoDB can dramatically improve the performance of your queries—if used right. In this article, we'll walk through a practical example of how indexes affect query performance, using a simple book database.

We'll go from inserting mock data to analyzing query execution plans, adding indexes, and understanding index limitations.


1. Generate Sample Data

Let's start by populating a collection with mock book data:

// Choose the database
use mylibrary;

// Define helper functions (basic randomizer)
function getRandomElement(arr) {
  return arr[Math.floor(Math.random() * arr.length)];
}

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

var genres = ['Fiction', 'Non-Fiction', 'Science', 'Fantasy', 'Biography', 'History', 'Mystery', 'Romance'];
var formats = ['epub', 'pdf', 'txt', 'audio'];
var firstNames = ['John', 'Mary', 'Alice', 'Robert', 'Linda', 'Michael', 'Sarah', 'David'];
var lastNames = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'Garcia'];

for (var i = 0; i < 1000; i++) {
  var author = getRandomElement(firstNames) + " " + getRandomElement(lastNames);
  var title = "Book Title " + (i + 1);
  var book = {
    author: author,
    title: "title,"
    genre: getRandomElement(genres),
    format: getRandomElement(formats),
    num_of_pages: getRandomInt(50, 1000),
    year_when_published: getRandomInt(1900, 2025)
  };

  db.books.insertOne(book);
}
Enter fullscreen mode Exit fullscreen mode

2. Check Query Execution Without Index

Let’s look at the query plan for a simple .find() operation:

db.books.find().explain("executionStats")
Enter fullscreen mode Exit fullscreen mode
{
  queryPlanner: {
    plannerVersion: 1,
    namespace: 'mylibrary;.books',
    winningPlan: { stage: 'COLLSCAN' }
  },
  executionStats: {
    executionSuccess: true,
    executionTimeMillis: '0.794',
    planningTimeMillis: '0.079',
    executionStages: {
      stage: 'COLLSCAN',
      nReturned: '1915',
      executionTimeMillisEstimate: '0.614'
    }
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

This uses a COLLSCAN (collection scan), which means MongoDB is scanning every document—inefficient for large datasets.


3. Create an Index on the author Field

Let’s improve performance by indexing the author field:

db.books.createIndex({ author: 1 })
Enter fullscreen mode Exit fullscreen mode

4. List All Indexes

db.books.getIndexes()
Enter fullscreen mode Exit fullscreen mode
[
  { v: 4, key: { _id: 1 }, name: '_id_', ns: 'mylibrary;.books' },
  {
    v: 4,
    key: { author: 1 },
    name: 'author_1',
    ns: 'mylibrary;.books'
  }
]
Enter fullscreen mode Exit fullscreen mode

5. Query by Author

db.books.find({ author: "John Smith" }).limit(5).pretty()
Enter fullscreen mode Exit fullscreen mode

6. Query Plan for Author Search

db.books.find({ author: "John Smith" }).explain("executionStats")
Enter fullscreen mode Exit fullscreen mode
{
  queryPlanner: {
    ...
    winningPlan: { stage: 'IXSCAN', indexName: 'author_1', direction: 'forward' }
  },
  executionStats: {
    executionSuccess: true,
    executionTimeMillis: '0.293',
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ The query now uses an IXSCAN (index scan) which is much faster!


7. Query by Title (No Index)

db.books.find({ title: "Book Title 10" }).explain("executionStats")
Enter fullscreen mode Exit fullscreen mode
{
  queryPlanner: {
    ...
    winningPlan: { stage: 'COLLSCAN' }
  },
  executionStats: {
    executionSuccess: true,
    executionTimeMillis: '1.775',
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Still doing a full collection scan—not ideal.


8. Query by Author + Title

db.books.find({ author: "John Smith", title: "Book Title 10" }).explain("executionStats")
Enter fullscreen mode Exit fullscreen mode
{
  queryPlanner: {
    ...
    winningPlan: { stage: 'IXSCAN', indexName: 'author_1', direction: 'forward' }
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

This is better than a full scan, but still not ideal if you're frequently querying by title.


9. Compound Index on year_when_published and title

db.books.createIndex({ year_when_published: 1, title: 1 })

db.books.getIndexes()
Enter fullscreen mode Exit fullscreen mode
[
  ...
  {
    v: 4,
    key: { year_when_published: 1, title: 1 },
    name: 'year_when_published_1_title_1',
    ns: 'mylibrary;.books'
  }
]
Enter fullscreen mode Exit fullscreen mode

Query plan now improves:

db.books.find({ title: "Book Title 10" }).explain("executionStats")
Enter fullscreen mode Exit fullscreen mode
{
  queryPlanner: {
    ...
    winningPlan: {
      stage: 'IXSCAN',
      indexName: 'year_when_published_1_title_1',
      ...
    }
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

10. Index Size Limitations

Let's test MongoDB's index size limit by trying to insert a document with a huge title:

var longTitle = "A".repeat(5000); // 5000-character string

db.books.insertOne({
  title: longTitle,
  author: "Test Author",
  year_when_published: 2020,
  genre: "Fiction",
  format: "pdf",
  num_of_pages: 300
});
Enter fullscreen mode Exit fullscreen mode

Error:

MongoServerError: key too large to index. Index name: year_when_published_1_title_1
Enter fullscreen mode Exit fullscreen mode

🚫 MongoDB’s index key size is limited (1024 bytes). Long strings in indexed fields can break inserts if the index becomes too large.


🔚 Conclusion

  • Indexes can drastically improve query performance.
  • Use .explain("executionStats") to analyze and optimize.
  • Compound indexes are powerful, but mindful of their field sizes.
  • Avoid indexing large strings unless absolutely necessary.

Top comments (0)