MongoDB performance optimization requires a systematic approach to identify bottlenecks, analyze query patterns, and implement effective indexing strategies. This guide covers profiling techniques, index optimization, and load testing methodologies.
Understanding MongoDB performance
MongoDB performance depends on several factors:
- Query patterns: How queries are structured and what operations they perform
- Index usage: Whether queries can leverage indexes effectively
- Data volume: Size of collections and documents
- Hardware resources: CPU, memory, and disk I/O capacity
- Connection pooling: How applications manage database connections
Profiling MongoDB queries
Enable profiling
MongoDB’s profiler collects detailed information about query execution:
// Enable profiling for slow queries (>100ms)
db.setProfilingLevel(1, { slowms: 100 });
// Profile all operations (use with caution in production)
db.setProfilingLevel(2);
// Disable profiling
db.setProfilingLevel(0);
Analyze profiler data
// Find slowest queries
db.system.profile.find().sort({ millis: -1 }).limit(10).pretty();
// Find queries without indexes
db.system.profile.find({ "execStats.stage": "COLLSCAN" }).pretty();
// Analyze query patterns
db.system.profile.aggregate([
{ $group: {
_id: "$command",
avgDuration: { $avg: "$millis" },
count: { $sum: 1 }
}},
{ $sort: { avgDuration: -1 } }
]);
Index optimization strategies
1. Compound indexes
Create indexes that match your query patterns:
// Query: find users by email and status
db.users.find({ email: "[email protected]", status: "active" });
// Optimal index
db.users.createIndex({ email: 1, status: 1 });
// Query with sort
db.users.find({ email: "[email protected]" }).sort({ createdAt: -1 });
// Index should include sort field
db.users.createIndex({ email: 1, createdAt: -1 });
2. Index selectivity
Prioritize indexes on high-selectivity fields:
// High selectivity: email (unique or near-unique)
db.users.createIndex({ email: 1 });
// Low selectivity: status (few distinct values)
// Use in compound indexes, not alone
db.users.createIndex({ status: 1, createdAt: -1 });
3. Partial indexes
Index only documents matching a condition:
// Index only active users
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { status: "active" } }
);
4. Text indexes
For full-text search:
db.articles.createIndex({ title: "text", content: "text" });
// Search
db.articles.find({ $text: { $search: "mongodb performance" } });
Query optimization techniques
1. Use projection to limit fields
// Bad: fetches all fields
db.users.find({ email: "[email protected]" });
// Good: only fetch needed fields
db.users.find(
{ email: "[email protected]" },
{ name: 1, email: 1, _id: 0 }
);
2. Limit result sets
// Always use limit for large collections
db.products.find({ category: "electronics" })
.sort({ price: 1 })
.limit(20);
3. Use covered queries
Queries that can be satisfied entirely from indexes:
// Index
db.orders.createIndex({ customerId: 1, orderDate: -1, total: 1 });
// Covered query (only uses index, no collection scan)
db.orders.find(
{ customerId: "12345" },
{ customerId: 1, orderDate: 1, total: 1, _id: 0 }
);
4. Avoid regex at start of string
// Bad: can't use index
db.users.find({ email: /^.*@example\.com$/ });
// Good: can use index
db.users.find({ email: /^user@example\.com$/ });
Load testing with JMeter
Setup JMeter for MongoDB
- Install MongoDB JDBC driver
- Configure JDBC connection
- Create test plans for common queries
Sample JMeter test plan
<!-- MongoDB connection configuration -->
<JDBCDataSource>
<stringProp name="dataSource">mongodb://localhost:27017/testdb</stringProp>
<stringProp name="queryType">Select Statement</stringProp>
<stringProp name="query">db.users.find({ email: "${email}" })</stringProp>
</JDBCDataSource>
Performance metrics to monitor
- Response time: Average, p90, p99 latencies
- Throughput: Queries per second
- Error rate: Failed queries percentage
- Resource usage: CPU, memory, disk I/O
Common performance issues
1. Collection scans
Symptom: Queries show COLLSCAN in execution stats
Solution: Create appropriate indexes
// Check for collection scans
db.users.find({ name: "John" }).explain("executionStats");
// If stage is "COLLSCAN", create index
db.users.createIndex({ name: 1 });
2. Inefficient indexes
Symptom: Index exists but query still slow
Solution: Verify index usage and order
// Check index usage
db.users.find({ email: "[email protected]", status: "active" })
.explain("executionStats");
// Ensure index field order matches query
db.users.createIndex({ email: 1, status: 1 }); // Correct order
3. Large documents
Symptom: High memory usage, slow queries
Solution: Normalize data, use references
// Instead of embedding large arrays
{
userId: "123",
orders: [/* 1000 orders */]
}
// Use references
{
userId: "123"
}
// Separate orders collection
{
userId: "123",
orderId: "456",
// order details
}
Monitoring and maintenance
Key metrics to track
- Query execution time: Average and p99 latencies
- Index hit ratio: Percentage of queries using indexes
- Collection scan ratio: Percentage of queries scanning collections
- Connection pool usage: Active vs available connections
- Cache hit ratio: WiredTiger cache efficiency
Regular maintenance tasks
- Analyze slow queries: Review profiler data weekly
- Review index usage: Remove unused indexes
- Update statistics: Run
db.collection.stats()regularly - Monitor disk space: Indexes consume storage
- Optimize sharding: For large deployments
Best practices summary
- Profile first: Enable profiling to identify slow queries
- Index strategically: Create indexes based on query patterns
- Use compound indexes: Match query and sort patterns
- Limit result sets: Always use
.limit()for large collections - Project fields: Only fetch needed fields
- Monitor continuously: Set up alerts for performance degradation
- Test under load: Use JMeter or similar tools for load testing
- Review regularly: Performance optimization is ongoing
Conclusion
MongoDB query optimization is an iterative process that requires understanding your application’s query patterns, creating appropriate indexes, and continuously monitoring performance. Start with profiling to identify bottlenecks, then optimize indexes and queries based on real usage patterns.
Remember: Measure, optimize, test, repeat. Performance tuning is not a one-time task but an ongoing practice.
