Sunday, 4 October 2026

MongoDB : What DBAs Must Get Right

MongoDB often looks simple during the first few application releases. Developers can add fields without waiting for a table change, nested-objects map naturally to application code, and a complete business record can be retrieved without several joins.
The real test starts after the collection grows, multiple services begin writing to it, and the workload becomes operationally important. One service stores an identifier as a string while another writes it as a number. Arrays grow without limits. Queries that were fast during development begin examining millions of documents. A secondary falls behind during a batch update, or a shard key concentrates most traffic on one node.

MongoDB is not difficult because it uses documents instead of tables. It becomes difficult when flexible modelling is treated as a replacement for data governance, indexing, recovery planning, and capacity management. This article looks at MongoDB from a production DBA perspective and focuses on the design and operational decisions that usually determine whether the platform remains stable.

Flexible Schema Does Not Mean No Schema

MongoDB stores data as BSON documents inside collections. BSON supports nested documents, arrays, dates, decimal values, binary data, and several numeric types. This allows an application object to be stored close to the form in which the application uses it.

A customer profile, for example, may contain contact details, preferences, addresses, and status information in one document. That can remove several joins and reduce application round trips.

The problem is not flexibility itself. The problem is uncontrolled variation. Without validation, different application versions may gradually introduce conflicting field names, missing attributes, and inconsistent data types.

use customer360

db.createCollection("profiles", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["customerId", "email", "status", "createdAt"],
properties: {
customerId: {
bsonType: "string"
},
email: {
bsonType: "string"
},
status: {
enum: ["ACTIVE", "SUSPENDED", "CLOSED"]
},
createdAt: {
bsonType: "date"
}
}
}
},
validationLevel: "strict",
validationAction: "error"
})

db.profiles.aggregate([
{
$project: {
customerIdType: { $type: "$customerId" },
emailType: { $type: "$email" }
}
},
{
$group: {
_id: {
customerIdType: "$customerIdType",
emailType: "$emailType"
},
documentCount: { $sum: 1 }
}
}
])

For an existing collection, inspect current documents before introducing strict validation. Enabling validation without understanding legacy data can reject application writes immediately after deployment.

A safer rollout is to identify inconsistent documents, correct the data, introduce validation in a controlled manner, and then enforce the final rules.

Model Around Business Operations

A relational design should not be copied into MongoDB collection by collection. That often creates a system dependent on repeated $lookup operations and multiple application calls.

The opposite approach is equally risky. Embedding every related object into one document can produce oversized documents, heavily updated arrays, and duplicated data that becomes difficult to maintain.

Use embedding when the data belongs together

Embedding works well when the related data is normally read together, updated together, and has a predictable size. Order line items are a common example because the description, quantity, and purchase price belong to the historical order.

Use references when the lifecycle is independent

A product master, audit trail, login history, or notification history usually changes independently and may grow continuously. Referencing such data from a separate collection is generally safer.

Unbounded arrays become operational debt

A customer document containing the last five login locations is manageable. A document containing every login since account creation will become increasingly expensive to read, update, index, replicate, and back up.

MongoDB documents also have a size limit. Even before that limit is reached, a growing document can create write amplification and contention. A practical design is to keep a small summary in the parent document and store the complete history separately.

db.orders.aggregate([
  {
    $project: {
      orderId: 1,
      itemCount: {
        $size: {
          $ifNull: ["$items", []]
        }
      },
      documentSizeBytes: {
        $bsonSize: "$$ROOT"
      }
    }
  },
  {
    $sort: {
      documentSizeBytes: -1
    }
  },
  {
    $limit: 20
  }
])





Indexes Still Control Query Performance

MongoDB does not remove the need for careful access-path design. A missing or poorly ordered index produces familiar symptoms: high CPU, excessive storage reads, slow application calls, cache pressure, and replica lag.

Consider an order service that frequently filters by tenant and status, then sorts by creation time.

db.orders.createIndex(
  {
    tenantId: 1,
    status: 1,
    createdAt: -1
  },
  {
    name: "idx_orders_tenant_status_created"
  }
)

db.orders.explain("executionStats").find({
tenantId: "TENANT-110",
status: "OPEN",
createdAt: {
$gte: ISODate("2026-07-01T00:00:00Z")
}
}).sort({
createdAt: -1
}).limit(100)

db.orders.aggregate([
{ $indexStats: {} },
{ $sort: { accesses: 1 } }
])

An IXSCAN stage does not automatically mean the query is efficient. Review the relationship between nReturned, totalKeysExamined, and totalDocsExamined.

If a query returns 100 documents after examining several million keys, the index is being used, but the access path is still expensive.

Indexing mistakes seen in production

  • Creating an index for every application filter without measuring write overhead.
  • Selecting the wrong order for fields in a compound index.
  • Removing an index because it appears unused during a short monitoring window.
  • Ignoring different BSON types in the indexed field.
  • Adding more indexes instead of correcting an unsuitable document model.

Replica Sets: High Availability Needs More Than Three Nodes

A replica set usually contains one primary and multiple secondaries. The primary accepts writes, and secondaries copy changes from the replication oplog. If the primary becomes unavailable, eligible members hold an election and one secondary may be promoted.

This architecture provides redundancy, but durability depends on write concern. A three-member replica set does not prove that every acknowledged write has reached multiple nodes.

db.orders.insertOne(
  {
    orderId: "ORD-2026-10019",
    status: "CREATED",
    createdAt: new Date()
  },
  {
    writeConcern: {
      w: "majority",
      wtimeout: 5000
    }
  }
)

rs.status()
rs.printReplicationInfo()
rs.printSecondaryReplicationInfo()

db.adminCommand({
replSetGetStatus: 1
})

Majority acknowledgment provides stronger durability but may add latency. A write concern timeout also requires careful application handling. It means the requested acknowledgment was not received within the configured time. It does not always prove that the write was never applied.

For this reason, retry logic should be idempotent. Retrying an order creation, payment request, or account update without a business key can create duplicates.

Replica lag is usually a capacity problem

A lagging secondary is often receiving changes correctly but cannot apply them as quickly as the primary generates them. Common causes include slow storage, network problems, large batch updates, index maintenance, insufficient CPU, cache eviction pressure, and competing reporting or backup activity.

Increasing oplog size may provide more recovery time, but it does not fix a secondary whose replay capacity remains lower than the primary write rate.

WiredTiger: Look Beyond Cache Usage

WiredTiger is responsible for storage, compression, cache management, checkpoints, concurrency, and journal-based recovery. The implementation differs from Oracle and PostgreSQL, but the production questions are familiar.

  • Does the active working set fit in memory?
  • Are dirty pages being written efficiently?
  • Is storage latency affecting checkpoints or eviction?
  • Is a write-heavy workload overwhelming the node?
const status = db.serverStatus()

status.mem
status.wiredTiger.cache
status.wiredTiger.transaction
status.wiredTiger.log

# Operating-system checks

iostat -xz 1 10
vmstat 1 10
pidstat -d -p "$(pidof mongod)" 1 10

grep -E "WTCHKPT|WTEVICT|WTRECOV|Slow query" 
/var/log/mongodb/mongod.log

High cache usage is not automatically a fault. A database cache is expected to remain busy. The warning signs are high cache pressure combined with aggressive eviction, growing dirty bytes, rising disk latency, checkpoint delays, and increasing application response time.

Increasing cacheSizeGB should not be the first response. More WiredTiger cache can reduce memory available to the filesystem cache, the operating system, connections, index builds, and aggregations.





Transactions Should Not Hide a Poor Document Model

MongoDB supports multi-document transactions on replica sets and sharded clusters. They are useful when several independent documents must change as one business operation.

They are not a substitute for proper modelling. If most application requests require updates across several collections, the design may be reproducing a relational system without the natural advantages of a relational database.

Long-running transactions can increase cache and storage pressure, retain older document versions for longer, experience more write conflicts, and become more expensive when multiple shards are involved.

Sharding Starts with the Shard Key

Replica sets provide redundancy, but one primary still has finite CPU, memory, storage, and write capacity. Sharding distributes collection data across multiple shards. Each shard is normally a replica set, applications connect through mongos, and the config server replica set maintains cluster metadata.

The shard key determines how data is distributed and how queries are routed. A poor key can create a hot shard, uneven storage, excessive chunk movement, or scatter-gather queries that contact every shard.

use commerce

db.orders.createIndex({
tenantId: 1,
orderDate: 1
})

sh.enableSharding("commerce")

sh.shardCollection(
"commerce.orders",
{
tenantId: 1,
orderDate: 1
}
)

sh.status()
db.orders.getShardDistribution()

Shard-key warning signs

  • Low-cardinality fields such as status or a small number of regions.
  • Monotonically increasing values that concentrate new writes.
  • A shard key missing from common application queries.
  • Tenant-based sharding when one tenant generates most of the traffic.
  • Hashed sharding selected without considering range-query requirements.

The balancer can move chunks between shards, but it cannot correct a workload that continually targets one logical key range. Shard-key selection should use actual query patterns, tenant distribution, write behaviour, and projected growth.

Practical Failure Scenario: Replica Lag During Batch Processing

Problem

A three-member replica set supported an order-processing application. During a nightly batch update, the primary remained responsive, but one secondary gradually fell more than 15 minutes behind. Since every process was running, the environment initially appeared healthy.

Root cause

The batch job issued millions of small updates. Each update modified fields covered by several indexes. The primary also used faster storage than the lagging secondary. Oplog generation increased faster than the secondary could apply the changes.

Fix

  • Converted individual operations into controlled bulk updates.
  • Reduced batch size and introduced pauses between batches.
  • Removed one redundant index after workload validation.
  • Aligned storage performance across replica-set members.
  • Increased oplog capacity as temporary protection.
  • Added alerts for apply-rate divergence and oplog-window reduction.

Increasing the oplog alone would only have delayed the problem. The permanent fix was to reduce write amplification and improve secondary replay capacity.

MongoDB Versus Oracle and PostgreSQL

Workload Requirement MongoDB Oracle or PostgreSQL
Variable product attributes Natural fit when different product categories require different fields and the document structure changes frequently. Supported through relational modelling, JSON columns, or a hybrid design, although highly variable attributes may require additional schema and query planning.
Nested content records Strong fit when related data can be stored and retrieved as a single document, reducing the need for joins. Well suited when nested information must be normalized, independently maintained, or queried across multiple entities. JSON can also be used where appropriate.
Customer profile aggregation Well suited for entity-centric profiles that combine preferences, addresses, activity summaries, and other related information in one document. A strong option when profile data is shared across normalized entities or requires complex reporting, joins, and strict relationship management.
Core financial ledger Supports ACID transactions, but is usually not the first choice for a highly relational ledger requiring extensive constraints, reconciliation, and audit controls. Usually the safer starting point because of mature transaction handling, strong consistency, relational constraints, locking controls, and established auditing capabilities.
Complex cross-entity reporting Aggregation pipelines can support advanced analysis, but repeated joins across many collections may indicate that the data model is becoming relational. Strong fit for complex joins, window functions, analytical SQL, materialized views, and reporting across multiple related entities.
Strong relational integrity Schema validation is available, but relationships between collections are generally enforced by the application or supporting services. Strong fit because primary keys, foreign keys, unique constraints, checks, and transactional rules can be enforced directly by the database.
Rapid document evolution Natural fit when fields are introduced gradually and multiple document versions must coexist during application changes. Possible through controlled schema migrations or JSON-based designs, but relational changes normally require more formal deployment and compatibility planning.
Heavy distributed document writes A strong candidate when writes can be distributed through sharding, provided the shard key avoids hotspots and supports the main query patterns. Can scale through partitioning, replication, distributed extensions, or platform-specific architectures, but horizontal write scaling is not usually as transparent as native document sharding.

MongoDB is a strong option for product catalogues, customer profiles, content platforms, device metadata, and other hierarchical records. Oracle or PostgreSQL is usually the safer starting point for financial ledgers, complex cross-entity reporting, strict foreign-key enforcement, and heavily normalized transactional systems.

Production Lessons for DBAs

  • Flexible schema moves responsibility to validators, application contracts, and release governance.
  • Unbounded arrays should be corrected before they become storage and replication problems.
  • An index can be used and still be inefficient. Always compare examined data with returned data.
  • Replica lag should be analysed as a difference between write generation and replay capacity.
  • A successful election does not prove the application reconnected successfully.
  • Sharding should begin only after a shard key has been tested against real workload patterns.
  • Replication protects availability. Restore-tested backups protect recoverability.

When MongoDB Is the Better Choice

MongoDB is a strong candidate when the data is naturally represented as a complete document and is usually read or changed as one unit.

Typical examples include:

  • Product catalogs with category-specific attributes
  • Customer profiles
  • Content management platforms
  • Configuration repositories
  • Mobile and web application backends
  • Session and preference data
  • Device metadata
  • Case-management records
  • APIs returning hierarchical objects

It is especially useful when new fields must be introduced frequently and application teams cannot coordinate repeated relational schema migrations.

That flexibility should still be supported by:

  • Schema ownership
  • Versioning rules
  • Validation
  • Index reviews
  • Retention policies
  • Backup and recovery testing

When Oracle or PostgreSQL Is the Safer Choice

MongoDB is not the natural first choice for every modern application.

A relational database is usually safer when the workload depends on:

  • Strong foreign-key enforcement
  • Complex joins across many entities
  • Highly normalized master data
  • Frequent multi-entity transactions
  • Financial ledger behavior
  • Mature SQL reporting
  • Complex ad hoc analytics
  • Strict database-side constraints

A payment ledger, general ledger, securities position store, or ERP accounting engine should not be moved to MongoDB only because the development team prefers JSON.

MongoDB can technically support many of these operations. Technical possibility is not the same as operational suitability.


Frequently Asked Questions

Does MongoDB really have no schema?

No. MongoDB has a flexible schema. Documents in the same collection can have different structures by default, but production systems still require an agreed document model. Collection validation can enforce critical rules.

Why is a query slow even when it uses an index?

The index may still examine too many keys or fetch too many documents. Check execution statistics, compound index order, sort behaviour, field data types, and predicate selectivity.

Can applications read from secondaries?

Yes, with an appropriate read preference. Secondary reads may be behind the primary, so read preference and read concern must match the application's consistency requirements.

Should every write use majority write concern?

Not automatically. Majority acknowledgment provides stronger durability but may increase latency and affect write availability during member failures. The choice should follow the business impact of losing, delaying, or retrying a write.

When should a collection be sharded?

Shard when one replica set can no longer meet projected storage or throughput requirements and the workload has a suitable shard key. Do not shard only because the collection is expected to grow.

Conclusion

MongoDB performs well when the workload is genuinely document-oriented and the operating model is designed around that reality. Flexible documents can simplify application development, replica sets provide high availability, and sharding offers a practical route beyond the limits of one server.

Those benefits do not remove the need for database engineering. Reliable MongoDB platforms define ownership for document structure, validate important fields, review execution plans before adding indexes, test elections with real application traffic, and monitor replication as a throughput problem rather than a simple process state.

The operational mindset remains similar to Oracle and PostgreSQL. Capacity planning, workload baselines, recovery testing, index discipline, and failure simulation still matter. The terminology changes to oplog, write concern, WiredTiger cache, checkpoints, and shard keys, but the DBA questions remain familiar: what can be lost, what can become slow, what can fail over, and how quickly can the service recover?

Review the largest collections, inspect slow queries with execution statistics, test application behaviour during elections, validate backup restores, and challenge every proposed shard key before the platform reaches its scaling limit.



No comments:

Post a Comment