Mongoose performance optimization is key to developing efficient applications. Through proper configuration and best practices, you can significantly improve query speed and overall performance.
Connection Optimization
Connection Pool Configuration
javascriptmongoose.connect('mongodb://localhost:27017/mydb', { maxPoolSize: 100, // Maximum connections minPoolSize: 10, // Minimum connections socketTimeoutMS: 45000, // Socket timeout serverSelectionTimeoutMS: 5000, // Server selection timeout connectTimeoutMS: 10000 // Connection timeout });
Connection Reuse
javascript// Establish connection when application starts mongoose.connect('mongodb://localhost:27017/mydb'); // Don't frequently close and reconnect // Avoid creating new connections on every request
Index Optimization
Creating Indexes
javascriptconst userSchema = new Schema({ email: { type: String, index: true, // Single field index unique: true }, name: { type: String, index: true }, age: Number, status: String }); // Compound index userSchema.index({ status: 1, age: -1 }); // Text index userSchema.index({ name: 'text', bio: 'text' }); // Geospatial index userSchema.index({ location: '2dsphere' });
Index Strategy
- Create indexes for frequently queried fields
- Use compound indexes to optimize multi-field queries
- Avoid too many indexes affecting write performance
- Regularly analyze query performance and optimize indexes
javascript// Analyze query plan const query = User.find({ email: 'john@example.com' }); const explanation = await query.explain('executionStats'); console.log(explanation.executionStats);
Query Optimization
Use lean()
javascript// Return plain JavaScript objects, better performance const users = await User.find().lean(); // Use lean() for read-only queries const users = await User.find({ status: 'active' }).lean();
Selective Querying
javascript// Only query needed fields const users = await User.find() .select('name email age') .lean(); // Exclude large fields const users = await User.find() .select('-largeField -anotherLargeField');
Limit Result Count
javascript// Use limit to restrict returned count const users = await User.find() .limit(100); // Implement pagination const page = 1; const pageSize = 20; const users = await User.find() .skip((page - 1) * pageSize) .limit(pageSize);
Use Projection
javascript// Projection reduces data transfer const users = await User.find( { status: 'active' }, { name: 1, email: 1, _id: 0 } );
Batch Operations
Batch Insert
javascript// Use insertMany instead of multiple insertOne const users = await User.insertMany([ { name: 'John', email: 'john@example.com' }, { name: 'Jane', email: 'jane@example.com' }, // ... more users ]);
Batch Update
javascript// Use updateMany instead of multiple updateOne await User.updateMany( { status: 'pending' }, { status: 'active' } );
Batch Delete
javascript// Use deleteMany instead of multiple deleteOne await User.deleteMany({ status: 'deleted' });
Caching Strategy
Query Caching
javascriptconst userSchema = new Schema({ name: String, email: String }, { query: { cache: true } }); // Enable caching const users = await User.find().cache(); // Set cache time const users = await User.find().cache(60); // 60 seconds
Application Layer Caching
javascriptconst NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 600 }); // 10 minute cache async function getUserById(userId) { const cacheKey = `user:${userId}`; let user = cache.get(cacheKey); if (!user) { user = await User.findById(userId).lean(); if (user) { cache.set(cacheKey, user); } } return user; }
Data Model Optimization
Embedding vs Reference
javascript// Embedding for one-to-one or one-to-many, small child documents const userSchema = new Schema({ name: String, profile: { bio: String, avatar: String } }); // Reference for one-to-many or many-to-many, large child documents const postSchema = new Schema({ title: String, author: { type: Schema.Types.ObjectId, ref: 'User' } });
Avoid Deep Nesting
javascript// Avoid overly deep nested structures // Not recommended const badSchema = new Schema({ level1: { level2: { level3: { level4: { data: String } } } } }); // Recommended: Flatten structure const goodSchema = new Schema({ level1: String, level2: String, level3: String, level4: String });
Monitoring and Tuning
Query Performance Monitoring
javascript// Enable debug mode mongoose.set('debug', true); // Custom debug function mongoose.set('debug', (collectionName, method, query, doc) => { console.log(`${collectionName}.${method}`, JSON.stringify(query)); });
Slow Query Logging
javascript// Log slow queries mongoose.connection.on('connected', () => { mongoose.connection.db.admin().command({ profile: 1, slowms: 100 // Queries over 100ms }); });
Best Practices Summary
- Connection Management: Use connection pools, avoid frequent connect/disconnect
- Index Optimization: Create appropriate indexes for frequently queried fields
- Query Optimization: Use lean(), selective queries, limit results
- Batch Operations: Use batch operations instead of multiple single operations
- Caching Strategy: Reasonably use query cache and application layer cache
- Data Model: Choose embedding or reference based on access patterns
- Monitoring and Tuning: Continuously monitor query performance and optimize timely
- Avoid N+1 Queries: Design data structure properly, avoid loop queries