Back to Blog

MongoDB Best Practices: Building Scalable and Performant Applications

October 14, 2025By Timothy Murphy
MongoDB Best Practices: Building Scalable and Performant Applications

MongoDB has revolutionized how developers think about databases with its flexible document model and horizontal scalability. However, to get the most out of MongoDB, you need to follow best practices that ensure performance, reliability, and maintainability. Let's explore the essential techniques for building robust MongoDB applications.

Schema Design Fundamentals

Unlike traditional relational databases, MongoDB's schema-less nature gives you flexibility - but with great power comes great responsibility.

Embed vs Reference: The Golden Rule

Embed when:

  • Data is accessed together
  • One-to-few relationships
  • Data doesn't change frequently
// Good: Embedded design for blog post with comments
{
  "_id": ObjectId("..."),
  "title": "MongoDB Best Practices",
  "author": "John Doe",
  "comments": [
    {
      "user": "Alice",
      "text": "Great article!",
      "date": ISODate("2025-10-15")
    },
    {
      "user": "Bob",
      "text": "Very helpful",
      "date": ISODate("2025-10-15")
    }
  ]
}

Reference when:

  • Data is accessed independently
  • One-to-many or many-to-many relationships
  • Data changes frequently
// Good: Referenced design for user and orders
// Users collection
{
  "_id": ObjectId("user123"),
  "name": "John Doe",
  "email": "[email protected]"
}

// Orders collection
{
  "_id": ObjectId("order456"),
  "userId": ObjectId("user123"),
  "items": [...],
  "total": 299.99
}

Indexing Strategies

Proper indexing is crucial for MongoDB performance. Without indexes, MongoDB must scan every document in a collection.

Create Indexes for Common Queries

// Create single field index
db.users.createIndex({ email: 1 })

// Compound index for complex queries
db.orders.createIndex({ userId: 1, createdAt: -1 })

// Text index for full-text search
db.articles.createIndex({ title: "text", content: "text" })

// Unique index to prevent duplicates
db.users.createIndex({ email: 1 }, { unique: true })

Index Best Practices

  1. Use Covered Queries: Include all queried fields in the index
  2. Monitor Index Usage: Use explain() to analyze query performance
  3. Avoid Over-Indexing: Each index slows down writes
  4. Use Compound Indexes Wisely: Order matters!
// Analyze query performance
db.users.find({ email: "[email protected]" }).explain("executionStats")

Query Optimization

Use Projection to Limit Returned Fields

// Bad: Returns all fields
db.users.find({ status: "active" })

// Good: Returns only needed fields
db.users.find(
  { status: "active" },
  { name: 1, email: 1, _id: 0 }
)

Limit Results and Use Pagination

// Implement efficient pagination
const pageSize = 20;
const page = 1;

db.products.find()
  .sort({ createdAt: -1 })
  .skip(pageSize * (page - 1))
  .limit(pageSize)

Use Aggregation Pipeline for Complex Operations

// Calculate average order value by category
db.orders.aggregate([
  {
    $match: { status: "completed" }
  },
  {
    $group: {
      _id: "$category",
      avgValue: { $avg: "$total" },
      count: { $sum: 1 }
    }
  },
  {
    $sort: { avgValue: -1 }
  }
])

Data Modeling Patterns

Pattern 1: Bucket Pattern (Time-Series Data)

Group related time-series data into buckets to reduce document count.

// Instead of one document per measurement
// Bad approach
{
  "sensorId": "sensor1",
  "timestamp": ISODate("2025-10-15T10:00:00Z"),
  "temperature": 22.5
}

// Good: Bucket pattern
{
  "sensorId": "sensor1",
  "date": ISODate("2025-10-15"),
  "measurements": [
    { "time": "10:00", "temp": 22.5 },
    { "time": "10:01", "temp": 22.6 },
    { "time": "10:02", "temp": 22.4 }
  ]
}

Pattern 2: Extended Reference Pattern

Include frequently accessed fields from referenced documents.

// Orders collection with extended user reference
{
  "_id": ObjectId("order123"),
  "user": {
    "id": ObjectId("user456"),
    "name": "John Doe",  // Cached from users collection
    "email": "[email protected]"  // Cached for quick access
  },
  "items": [...],
  "total": 199.99
}

Pattern 3: Subset Pattern

Store only the most relevant subset of data in embedded documents.

// Product with subset of reviews
{
  "_id": ObjectId("product789"),
  "name": "Laptop",
  "price": 999,
  "recentReviews": [
    // Only store last 10 reviews here
    { "rating": 5, "comment": "Excellent!" }
  ],
  "reviewCount": 1543,  // Total count
  "avgRating": 4.7
}

Write Concerns and Read Preferences

Write Concerns

Control acknowledgment behavior for write operations.

// Default write concern (w:1)
db.users.insertOne(
  { name: "Alice" }
)

// Majority write concern (safer)
db.orders.insertOne(
  { userId: "123", total: 99.99 },
  { writeConcern: { w: "majority", wtimeout: 5000 } }
)

// No acknowledgment (fastest, but risky)
db.logs.insertOne(
  { message: "User logged in" },
  { writeConcern: { w: 0 } }
)

Read Preferences

Control which replica set members to read from.

// Read from primary (default)
db.users.find().readPref("primary")

// Read from secondary (reduce primary load)
db.analytics.find().readPref("secondary")

// Read from nearest member (lowest latency)
db.products.find().readPref("nearest")

Transactions and Atomicity

MongoDB supports multi-document ACID transactions (since version 4.0).

// Use transactions for operations that must succeed or fail together
const session = db.getMongo().startSession();
session.startTransaction();

try {
  db.accounts.updateOne(
    { _id: "account1" },
    { $inc: { balance: -100 } },
    { session }
  );
  
  db.accounts.updateOne(
    { _id: "account2" },
    { $inc: { balance: 100 } },
    { session }
  );
  
  session.commitTransaction();
} catch (error) {
  session.abortTransaction();
  throw error;
} finally {
  session.endSession();
}

Connection Pooling

Always use connection pooling to reuse database connections efficiently.

// Node.js example with connection pooling
const { MongoClient } = require('mongodb');

const client = new MongoClient(uri, {
  maxPoolSize: 50,
  minPoolSize: 10,
  maxIdleTimeMS: 30000
});

await client.connect();

Backup and Disaster Recovery

Regular Backups

# Create backup using mongodump
mongodump --uri="mongodb://localhost:27017" --out=/backup/dump-$(date +%Y%m%d)

# Restore backup using mongorestore
mongorestore --uri="mongodb://localhost:27017" /backup/dump-20251015

Point-in-Time Recovery

Enable oplog for continuous backup with tools like MongoDB Atlas or Ops Manager.

Monitoring and Performance

Key Metrics to Monitor

  1. Operations per Second: Track read/write throughput
  2. Query Execution Time: Identify slow queries
  3. Connection Count: Monitor connection pool usage
  4. Replication Lag: Ensure secondaries stay in sync
  5. Disk Usage: Plan for storage growth

Enable Profiling

// Enable profiling for slow queries (>100ms)
db.setProfilingLevel(1, { slowms: 100 })

// View slow queries
db.system.profile.find().limit(10).sort({ ts: -1 })

Security Best Practices

Enable Authentication

// Create admin user
use admin
db.createUser({
  user: "admin",
  pwd: "strongPassword123",
  roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})

Use Role-Based Access Control

// Create application user with limited permissions
use myapp
db.createUser({
  user: "appUser",
  pwd: "appPassword",
  roles: [
    { role: "readWrite", db: "myapp" }
  ]
})

Enable Encryption

  • Use TLS/SSL for network encryption
  • Enable encryption at rest for sensitive data
  • Rotate credentials regularly

Common Pitfalls to Avoid

  1. Unbounded Array Growth: Use $slice or capped collections
  2. Large Documents: Keep documents under 16MB
  3. Missing Indexes: Always index queried fields
  4. N+1 Queries: Use $lookup or embed related data
  5. Not Using Bulk Operations: Batch inserts for better performance
// Good: Bulk insert
db.users.insertMany([
  { name: "Alice" },
  { name: "Bob" },
  { name: "Charlie" }
])

// Bad: Multiple single inserts
db.users.insertOne({ name: "Alice" })
db.users.insertOne({ name: "Bob" })
db.users.insertOne({ name: "Charlie" })

Conclusion

MongoDB's flexibility is both its greatest strength and potential weakness. By following these best practices, you'll build applications that are performant, scalable, and maintainable. Remember to:

  • Design schemas based on access patterns
  • Create indexes for all common queries
  • Use the aggregation pipeline for complex operations
  • Monitor performance regularly
  • Implement proper security measures

Manage MongoDB with Ease

Download LunoDB for an intuitive MongoDB management experience with query building, data visualization, and AI-powered assistance for writing complex queries.