Skip to content
Back to blog

MongoDB Notes

Organizes MongoDB data operations, modeling, replica sets, transactions and sharding, plus index optimization, monitoring, backup, and version upgrades.

Updated

Basic Commands

Inserting Data

// Insert a document into the home collection
// home is the collection name, used to group similar data documents
db.home.insertOne({ "name": 'He110' })

// Insert multiple documents
db.home.insertMany([
  { "name": 'He110' }
])

Querying Data

// Single-condition query
db.movies.find({
  "year": 1976
})

// Multi-condition AND query
db.movies.find({
  "year": 1967,
  "title": "红色娘子军"
})

// The strictly correct way to write a multi-condition AND query
db.movies.find({
  $and: [
    { "year": 1967 },
    { "title": "红色娘子军" }
  ]
})

// Multi-condition OR query
db.movies.find({
  $or: [
    { "year": 1967 },
    { "title": "红色娘子军" }
  ]
})

// Regex matching
db.movies.find({
  "title": /红色/
})

// Logical comparison
db.movies.find({
  "year": {
    // Not equal to
    $ne: 1,
    // Greater than
    $gt: 1967,
    // Greater than or equal to
    $gte: 1967,
    // Less than
    $lt: 1976,
    // Less than or equal to
    $lte: 1976,
  }
})

// Other conditions
db.movies.find({
  // Find documents where the year field does not exist
  "year": {
    $exists: false
  },
})
db.movies.find({
  "year": {
    // Find year == 1967 or year == 1976
    $in: [1967, 1976],
    // Find year != 1978
    $nin: [1978],
  },
  // Query where the from.country field === "china"
  "from.country": "china",
  // Write the property value separately
  "from": {
    // All defined properties must match
    $elemMatch: {
      "country": "china",
      "city": "beijing",
    }
  }
})

// Only get specified fields
db.movies.find(
  { "year": 1967 },
  // Do not return _id, only return title
  { "_id": false, "title": true }
)

Deleting Data

// Delete all documents with year < 0
db.movies.remove({ "year": { $lt: 0 } })

// Delete all documents
db.movies.remove({})

// Error: arguments are required
db.movies.remove()

Updating Data

// Update the first matching document
db.movies.updateOne(
  // Which documents to find
  {
    "year": 1967
  },
  // How to update this document
  {
    $set: 1976
  }
)

db.movies.updateOne(
  {},
  {
    // Append this data to the end
    $push: { "year": 1976, "title": "太阳" },
    // Append multiple pieces of data to the end
    $pushAll: [{ "year": 1976, "title": "太阳" }],
    // Do nothing if it exists, insert if it does not
    $addToSet: { "year": 1976, "title": "太阳" },
    // Remove data from the specified data: 1 removes the last one, -1 removes the first one
    $pop: { "tags": 1 },
    // Remove by value rather than by index
    $pull: { "tags": "糟粕" },
    // Batch operation of pull
    $pullAll: [{ "tags": "糟粕" }],
  }
)

// Update all matching documents
db.movies.updateMany([
  { "year": 1967 }
])

Dropping a Collection

db.movies.drop()

Aggregation Operations

db.users.aggregate([
  // Query conditions
  { $match: { "gender": "男" } },
  // Which row to start from
  { $skip: 100 },
  // How much data to take
  { $limit: 20 },
  {
    $project: {
      // Rename the first_name field to 名
      "名": "$first_name",
      // Rename the last_name field to 姓
      "姓": "$last_name"
    }
  }
])

The above command is equivalent to the following SQL:

SELECT
  first_name as '名',
  last_name as '姓'
FROM Users
WHERE gender='男'
SKIP 100
LIMIT 20

Unwinding Data

db.students.findOne()
// {
//   name: '张三',
//   score: [
//     { subject: '语文', score: 120 },
//     { subject: '数学', score: 120 },
//     { subject: '英语', score: 120 },
//   ]
// }

db.students.aggregate([
  { $unwind: '$score' }
])

// {
//   name: '张三',
//   score: { subject: '语文', score: 120 }
// }
// {
//   name: '张三',
//   score: { subject: '数学', score: 120 }
// }
// {
//   name: '张三',
//   score: { subject: '英语', score: 120 }
// }

Grouping

db.products.aggregate([
  {
    $bucket: {
      // Group by the price field
      groupBy: "$price",
      // Group intervals are [0, 10), [10, 20), [20, 30), [30, 40)
      boundaries: [0, 10, 20, 30, 40],
      // Those not in the intervals go into Other
      default: "Other",
      // Set the output format to `{ count: total }`
      output: { "count": { $sum: 1 } }
    }
  }
])
// Count the total amount of data grouped by price and by release time at the same time
db.products.aggregate([
  {
    $facet: {
      price: {
        $bucket: {
          // Group by the price field
          groupBy: "$price",
          // Group intervals are [0, 10), [10, 20), [20, 30), [30, 40)
          boundaries: [0, 10, 20, 30, 40],
          // Those not in the intervals go into Other
          default: "Other",
          // Set the output format to `{ count: total }`
          output: { "count": { $sum: 1 } }
        }
      },
      year: {
        $bucket: {
          // Group by the year field
          groupBy: "$year",
          // Group intervals are [2021, 2022), [2022, 2023), [2023, 2024), [2024, 2025)
          boundaries: [2021, 2022, 2023, 2024, 2025],
          // Those not in the intervals go into Other
          default: "Other",
          // Set the output format to `{ count: total }`
          output: { "count": { $sum: 1 } }
        }
      },
    }
  }
])

Data Computation

db.orders.aggregate([
  // Declare the statistical scope of the data
  {
    $match: {
      // Only count completed orders
      status: "completed",
      // Set the statistical time period
      orderDate: {
        // Count the full year of 2024 data
        $gte: ISODate("2024-01-01"),
        $lt: ISODate("2025-01-01"),
      },
    }
  },
  // Aggregate total amount, total shipping fee, total count
  {
    $group: {
      // Aggregate all data together, no grouping
      _id: null,
      // The total field equals the sum of each document's total
      total: { $sum: "$total" },
      // The shippingFee field equals the sum of each document's shippingFee
      shippingFee: { $sum: "$shippingFee" },
      // +1 each time
      count: { $sum: 1 }
    }
  },
  // Declare the output format
  {
    $project: {
      // Calculate the grand total
      grandTotal: {
        // Grand total = total amount - shipping fee
        $add: ["$total", "$shippingFee"]
      },
      // Keep the count field, continue to output it
      count: 1,
      // Discard the _id field, no longer output it
      _id: 0
    }
  },
])

Replica Set

Mechanism

  • When data is written, replicate it to other independent nodes
  • When the writing node fails, automatically elect a new replacement node

Because of the above mechanisms, MongoDB has the following capabilities:

  • Data distribution: off-site data synchronization, reducing read latency in another region
  • Read-write separation: different types of operations are executed on different nodes
  • Remote disaster recovery: quickly switch to a remote backup data center when a data center fails

Typical Structure

MongoDB generally forms a three-node cluster:

  • Primary node: only one; only the primary node can write
  • Secondary nodes: two or more; replicate the data from the primary node and provide data read operations

Every node can participate in election voting; besides that, there is the voting node Arbiter, used to break tie votes, but it is no longer recommended.

Data Replication

  • Primary node: after writing data, records it in the oplog
  • Secondary nodes: fetch the primary node's oplog, replay the primary node's operations locally, and synchronize data updates

Election

Nodes with voting rights send heartbeat packets to each other pairwise; if more than 5 heartbeat packets are lost, the node is judged as disconnected.

If the disconnected node is a secondary, it is ignored.

If the disconnected node is the primary, an election is triggered.

The election is implemented based on the RAFT consensus algorithm; the necessary condition for a successful election is that a majority of voting nodes are alive.

A replica set supports at most 50 nodes, but at most 7 nodes with voting rights.

To be elected as the primary node, the following conditions must be met:

  • Able to establish connections with a majority of nodes
  • Has a relatively new oplog
  • Has a relatively high priority (depends on whether it is configured)

Replica set nodes support the following configurations:

  • v: whether it has voting rights; if so, it participates in voting
  • priority: specifies the node priority; nodes with higher priority are more likely to become the primary. A priority of 0 cannot become the primary
  • hidden: the node can replicate data but is invisible to applications; priority must be 0, and it may have voting rights. Mainly used for backup operations.
  • slaveDelay: manually set the synchronization delay, synchronizing data from N seconds ago, keeping a time difference. Mainly used for rollback after mistaken operations.

Replica Set Configuration

# /data/db/mongod.conf
systemLog:
  destination: file           #  MongoDB's destination for all log output is specified as a file
  path: /data/db/mongod.log   # Log file location
  logAppend: true             # When mongod or a mongo instance restarts, mongod or mongos appends new entries to the end of the existing log file
storage:
  dbPath: /data/db            # Directory where data is stored
net:
  bindIp: 0.0.0.0             # IP to listen on
  port: 28017                 # Port to listen on
replication:                  # Specify replica set mode instead of a single node
  replSetName: rs0            # The unified identifier of the replica set; only MongoDB instances with the same identifier can form a cluster
processManagement:
  fork: true                  # Run as an independent background process instead of in the foreground
# Start MongoDB with the configuration file
mongod -f /data/db/mongod.conf
# Enter the created mongo shell
# mongo 8 changed the command to mongosh; use mongo for older versions
mongosh localhost:28017
// Initialize the cluster and enter cluster state
rs.initiate()

// View cluster status, including which secondary nodes there are
rs.status()

// Add a secondary node
rs.add("localhost:28018")

// Add a secondary node
rs.add("localhost:28018")

By writing through the parent node and reading from secondary nodes, you can verify whether the replica set is working properly. Note that the permissions of secondary nodes change after clustering

// On a secondary node, you cannot read via shell; you need to enable read permission first
rs.slaveOk()

// Now read operations can begin

Common Commands

// On a secondary node, you cannot read via shell; you need to enable read permission first
rs.slaveOk()

// Lock write or sync operations; data will no longer be updated, equivalent to being down
db.fsyncLock()
// Unlock write or sync operations; after recovery, data is synced via oplog replay, so you need to ensure the oplog data is complete, containing all data from lock to unlock
db.fsyncUnlock()

Data Model Design

Elements to consider in model design:

  • Entity: describes the main data collection of the business. For example, Person is an abstracted entity.
  • Attribute: describes a single piece of information inside an entity. For example, name is an attribute of the Person entity.
  • Relationship: describes the data rules between entities. For example, School and the entity have a many-to-many relationship.

The Three SQL Normal Forms

  1. Ensure all fields are atomic values; data cannot be further divided.
  2. Every non-key attribute fully depends on the primary key; content that does not depend on the primary key is split out into other tables.
  3. Every non-key attribute does not depend on other non-key attributes; those depended on by multiple tables are extracted into a separate table, connected via foreign keys and association tables.

MongoDB Data Model

Traditional data modeling goes through "conceptual model" -> "logical model" -> "physical model".

  • In the "conceptual model", user requirements are described.
  • In the "logical model", user requirements are broken down into implementable logic.
  • In the "physical model", SQL design normal forms are considered, and data is modeled according to the normal forms.

MongoDB can skip the "physical model" and directly model the "logical model", because MongoDB allows redundancy, does not require atomicity, and therefore has no additional restrictions.

Correspondingly, when updating fields, redundant fields also need to be considered for updating together.

MongoDB Model Design

Note: a document cannot exceed 16M; if the size exceeds the limit after embedding, it must be split.

Take contact management as an example for modeling.

Building the Base Model

The base model is built according to the following steps:

  1. Find the entities to be stored: derive the "logical model" from the "conceptual model".
  2. Clarify the relationships between entities: list the relationships between entities.
  3. Determine the data embedding approach: MongoDB has no foreign keys or association tables; it handles this through data redundancy instead, so how data is stored needs to be clarified.

Applied to contact management:

  • Objects: Contacts, Groups, Address, Avatars
  • Relationships:
    • Contacts - Avatars: one-to-one; each user has one avatar.
    • Contacts - Address: one-to-many; each user may have multiple addresses.
    • Contacts - Groups: many-to-many; each user can belong to multiple groups, and each group can have multiple users.
  • Embedding approach:
    • Avatars: one-to-one, embed the entire object directly as a sub-field into the Contacts data structure.
    • Address: one-to-many, also embedded directly into the object as an array.
    • Groups: embedded into the object as an array, implementing many-to-many with data redundancy.

Business Scenario Refinement

Clarify the following requirements and adjust the model:

  • The most frequent data query patterns
  • The most commonly used query parameters
  • The most frequent data write patterns
  • The read-write operation ratio
  • The data size

After clarifying the business requirements, use references to avoid performance bottlenecks, and use redundancy to optimize access performance.

Association Tables

Requirements:

  • Contact management is used for marketing
  • There are tens of millions of contact records
  • Groups are frequently modified, including: adding groups, changing group names, changing group descriptions, changing marketing status
  • A group may have millions of contacts

Analysis:

According to the previous model, embedding Group into Contacts would cause data redundancy, which would affect the performance of write operations.

When a group name is modified, it would be a million-level update: all data belonging to the same group needs to be updated.

Group can be split into a separate collection, associated via group_ids, and queried using $lookup across multiple collections

Query example:

db.contacts.aggregate([
  {
    $lookup: {
      // Declare the name of the associated collection
      from: "groups",
      // Which field in this collection is used to match the associated collection
      localField: "group_ids",
      // Which field of the associated collection is used to match this collection
      foreignField: "group_id",
      // Which key name is used to store the matched group data
      as: "groups"
    }
  }
])
Reference Pattern

Requirements:

  1. Users upload high-fidelity images, 5-10MB in size
  2. An uploaded avatar cannot be changed within one month
  3. The ratio of queries without avatars to queries with avatars is about 9:1

Analysis:

Since the avatar itself is not read frequently and its content is large, it can be extracted into a separate table and associated by reference, so that every query gets a considerable performance boost.

Query example:

db.contacts.aggregate([
  {
    $lookup: {
      // Declare the name of the associated collection
      from: "avatars",
      // Which field in this collection is used to match the associated collection
      localField: "avatar_id",
      // Which field of the associated collection is used to match this collection
      foreignField: "_id",
      // Which key name is used to store the matched group data
      as: "avatar"
    }
  }
])
Summary

Cases that need to be split out and stored separately:

  • The field stores large data, at the MB level or exceeding 16MB
  • The embedded object or array is frequently modified
  • The embedded array keeps growing without a limit

Notes on using $lookup:

  • MongoDB does not perform primary-foreign key checks on the lookup collection
  • Only left outer join is supported
  • from cannot be a sharded collection

Model Design Patterns

Bucket Design
  • Scenario: recording time-series data, such as IoT, smart cities, smart transportation
  • Pain point: data points are collected frequently and the data volume is large
  • Solution: merge fragmented data into one entity through embedded documents, reducing index size and redundant data usage

Requirement:

For an IoT scenario with massive drone monitoring data: 100k drones, one year of data, one record per minute.

{
  _id: '20160101050000:CA2790',
  icao: 'CA2790',
  callsign: 'CA2790',
  date: ISODate("2024-01-01T00:00:00.000+0000"),
  event: {
    a: 31428,
    b: 173,
    p: [115, 134],
    s: 91,
    v: 80
  }
}

Number of documents recorded: 100000 * 365 * 24 * 60 = 52560000000 ( 52.56 billion ) Index size: 52560000000 * 130 = 6832800000000 ( 6832.8 billion bytes, about 6364 GB ) Data size: 52560000000 * 92 = 4835520000000 ( 4835.52 billion bytes, about 4503 GB )

In other words, about 52 billion records per year, 10TB in size

The idea of bucket design is to aggregate the data: aggregate per-minute data into per-hour data, i.e., the following data structure:

{
  _id: '20160101050000:CA2790',
  icao: 'CA2790',
  callsign: 'CA2790',
  events: [
    {
      a: 31428,
      b: 173,
      p: [115, 134],
      s: 91,
      v: 80,
      date: ISODate("2024-01-01T00:00:00.000+0000")
    },
    {
      a: 11111,
      b: 222,
      p: [-115, -134],
      s: 91,
      v: 80,
      date: ISODate("2024-01-01T00:01:00.000+0000")
    }
  ]
}

Number of documents recorded after modification: 100000 * 365 * 24 = 876000000 ( 876 million ) Index size after modification: 876000000 * 130 = 113880000000 ( 113.88 billion bytes, about 106 GB ) Data size after modification: 876000000 * 92 = 80592000000 ( 80.592 billion bytes, about 618 GB )

The total volume is reduced from 10TB to 724GB.

Since per-minute data is rarely read individually in most cases, reading a full day's data actually benefits more ( 1440 reads reduced to 24 )

Transposing Columns to Rows
  • Scenario: many fields, each of which needs to be indexed
  • Pain point: the index size is very large
  • Solution: merge related fields into an array for display, so the number of indexes is greatly reduced

The source data structure is as follows:

{
  title: 'Dunkirk',
  release_USA: '2017/07/23',
  release_UK: '2017/08/01',
  release_France: '2017/08/01',
}

After transposing columns to rows:

{
  title: 'Dunkirk',
  release: [
    // Convert the column name into a key value
    {
      country: 'USA',
      date: '2017/07/23'
    },
    {
      country: 'UK',
      date: '2017/08/01',
    },
    {
      country: 'France',
      date: '2017/08/01',
    }
  ]
}
Version Field
  • Scenario: the data structure iterates frequently and multiple data structures coexist
  • Pain point: data of different versions stored together is hard to maintain
  • Solution: add a version number as the data structure identifier and adapt for different data versions
Approximate Computation
  • Scenario: statistics on site visit data and click data, with writes far exceeding reads
  • Pain point: frequent writes, unfriendly to MongoDB's read-write separation model; all pressure is on the master
  • Solution: approximate computation, e.g., take a random number (0, 9) each time, and update only when it equals 0, adding + 10 to the data per update; even if the data is inaccurate it doesn't matter much, since the need for performance is far higher than for accuracy
Pre-Aggregated Fields
  • Scenario: precise statistics such as performance rankings, game rankings, product statistics
  • Pain point: high resource consumption and long aggregation computation time
  • Solution: store statistical data additionally; on each update, besides updating the data itself, also update the statistical data
{
  product: 'milk',
  sku: 'xxx-yyy-zzz-123456',
  quantity: 12345,
  // Counted daily sales
  daily_sales: 40,
  // Counted weekly sales
  weekly_sales: 40,
  // Counted monthly sales
  monthly_sales: 40,
}

Update as follows when a sale occurs:

db.inventory.update(
  { _id: '123' },
  {
    $inc: {
      // Inventory -1
      quantity: -1,
      // Statistical fields + 1
      daily_sales: 1,
      weekly_sales: 1,
      monthly_sales: 1
    }
  }
)

Transactions

Write Operation Transactions

writeConcern

writeConcern determines how many nodes must be written for a write to be considered successful; the program initiating the write operation is blocked until the write succeeds.

  • 0: the write is initiated, and writing to master counts as success
  • 1-max node count: how many copies must be replicated for the write to count as successful
  • majority: replicated to more than half of the nodes for the write to count as successful
  • all: written to all nodes for the write to count as successful

When configured as 0, data may be lost; it is only used in scenarios requiring high performance and low reliability. The data loss chain is as follows:

  1. Write to master, return success.
  2. Master hangs, and none of the remaining nodes have replicated this write operation.

When configured as 1-max node count, since MongoDB may dynamically scale or nodes may crash.

Hard-coding the node count is not a good choice: the write may never succeed because a node hangs, or data may be lost because a majority of nodes have not synced after scaling. It is generally not recommended.

Configuring majority ensures a majority of nodes have written the data, but the corresponding program's write operation time becomes longer.

Configuring all absolutely ensures no data loss, but the corresponding program's write operation time becomes longer and may become a response bottleneck.

This needs to be considered together with slaveDelay, to avoid extended write operation time caused by manually limited delay

journal

MongoDB's write operations are divided into two parts:

  • Writing to memory
  • Writing to disk

To avoid frequent disk I/O, write operations are first saved to memory, and only written to disk for persistent storage after reaching a certain quantity or a timeout. Once persistently stored, data will not be lost even if the machine goes down.

journal can be configured for when a write operation is confirmed as complete:

  • true: only counts as complete after writing to disk.
  • false: counts as complete after writing to memory.

Notes

  • Generally, configuring majority is enough, balancing performance and consistency.
  • Try to avoid hard-coding the node count; even when configuring, do not configure all nodes, to avoid a single node hanging causing all write operations to fail.
  • writeConcern only increases the program's write operation time, without increasing MongoDB's write operation load
  • For ordinary data, { w: 1 } is enough; important data needs { w: 'majority' }

Read Operation Transactions

Due to MongoDB's distributed nature, reading data faces the following problems:

  • Which node to read from?
  • What data can be read?

Correspondingly, the following configurations can be used to solve them:

  • readPreference
  • readConcern

readPreference

readPreference determines which node is used for read operations. Optional values are as follows:

  • primary: only select the primary node, suitable for operations with extremely high real-time requirements, accepting only the latest data
  • primaryPreference: prefer the primary node; if the primary node is unavailable, read from a secondary node. Suitable for operations with relatively high real-time requirements, preferring the latest data
  • secondary: only select secondary nodes, suitable for operations with average real-time requirements, able to accept non-latest data
  • secondaryPreference: prefer secondary nodes; if secondary nodes are unavailable, read from the primary node. Suitable for operations with average real-time requirements, able to accept non-latest data
  • nearest: select the nearest node, considered for distributed cross-region clusters.
  • ${tag}: select nodes by node Tag. Operation separation can be achieved by tagging nodes: operations that need to read large amounts of data are distributed to nodes with better performance, while ordinary operations can directly use lower-tier nodes, or be separated by business to avoid businesses affecting each other.

readPreference supports different configuration methods such as configuration at initialization and configuration before reading:

  • Via MongoDB connection string parameters: mongodb://host1:27107,host2:27107,host3:27017/?replicaSet=rs&readPreference=secondary
  • Via MongoDB driver API: MongoCollection.withReadPreference(ReadPreference readPref)
  • Mongo Shell: db.collection.find(0).readPref("secondary") // Regardless of the current node, always read from a secondary node

Notes:

  • Try to use primaryPreference instead of primary, to avoid having no node to read from when the primary node hangs and an election is still in progress.
  • When using Tag, high availability needs to be considered; try to configure multiple nodes for each Tag
  • Tag needs to be considered together with voting rights and priority; generally a Tag with a specific function is not expected to become the primary node, so its priority is set to 0

readConcern

After readPreference selects the specified node, readConcern determines which data on this node is readable, similar to the isolation level of relational databases. Optional values include:

  • available: read all available data
  • local: read all available data belonging to the current shard
  • majority: read data that has been committed on a majority of nodes
  • linearizable: read documents linearizably
  • snapshot: read data from the most recent snapshot
available and local

In a replica set, there is no difference between available and local; the difference only exists in a sharded cluster, i.e., historical data migrated to be stored in other MongoDB instances.

Sharded clusters have the concept of data ownership, i.e., which MongoDB the current data belongs to. available does not consider the ownership issue, while local requires the ownership to be itself.

In the scenario of data migration between shards, the ownership does not change during the migration, so available and local behave inconsistently.

Notes:

  • local looks more reliable, but every read requires filtering the data; in some less important scenarios, consider switching to available
  • MongoDB <= 3.6 does not support configuring secondary nodes as local
  • When reading from the primary node, the default is local; when reading from secondary nodes, for forward compatibility, the default is available
majority

Only when a majority of nodes have replicated this piece of data; here the majority is determined according to the majority perceived by the node.

A write operation is unsafe before it reaches a majority of nodes: once the primary node crashes while the secondary nodes have not yet replicated the operation, the just-completed write is lost. Treating a write operation as a transaction, from a transaction perspective, the transaction can be considered rolled back.

The majority configuration avoids the dirty read problem, equivalent to Read Committed in SQL.

The majority judgment logic is as follows:

  1. If the own node has written, but a majority of nodes have not written, return historical data
  2. If the own node has written and a majority of nodes have written, but the own node has not received the write completion messages from other nodes, return historical data
  3. If the own node has written and has received write messages from a majority of nodes, return the new data

MongoDB maintains multiple versions of data snapshots, implementing this mechanism through the MVCC mechanism

Enabling majority needs to be turned on when configuring the node:

replication:
  replSetName: rs0
  enableMajorityReadConcern: true
linearizable

The primary node loses contact, a new primary node is elected, the new primary node accepts writes, and at this point reading the old primary node still returns the old data because the communication is disconnected — i.e., dirty data. majority cannot avoid this situation.

In this case, linearizable is needed: when reading, it confirms data validity with a majority of nodes, and only returns the new data after they all acknowledge the data is valid.

This configuration reduces read efficiency; it is recommended to set a timeout with maxTimeMS to avoid long blocking.

snapshot

The highest level of consistency: before the operation completes, new data cannot be read. It can achieve Repeatable Read in SQL

db.tx.insertMany([
  { x: 1 },
  { x: 2 },
])
var session = db.getMongo().startSession()
session.startTransaction({
  // Set the read operation isolation to the highest level
  readConcern: { level: 'snapshot' },
  // Set the write operation isolation to majority write
  writeConcern: { w: 'majority' },
})
var coll = session.getDatabase('test').getCollection('tx')
// View data inside the transaction
coll.find({ x: 1 }) // { x: 1 }
// Update data outside the transaction
db.tx.updateOne({ x: 1 }, { $set: { y: 1 } })
// View data outside the transaction
db.tx.find({ x: 1 }) // { x: 1, y: 1 }
// View data inside the transaction
coll.find({ x: 1 }) // { x: 1 }
// Roll back the data
session.abortTransaction()

Transaction Development

Support Status

Transaction property Support level
Atomicity Single-table documents: supported in 1.x
Multi-table multi-row in replica sets: supported in 4.0
Multi-table multi-row in sharded clusters: supported in 4.2
Consistency writeConcern, readConcern supported in 3.2
Isolation readConcern supported in 3.2
Durability Journal and Replication

Usage

db.tx.insertMany([
  { x: 1 },
  { x: 2 },
])
var session = db.getMongo().startSession()
session.startTransaction()
var coll = session.getDatabase('test').getCollection('tx')
// Update data inside the transaction
coll.updateOne({ x: 1 }, { $set: { y: 1 } })
// View data inside the transaction
coll.find({ x: 1 }) // { x: 1, y: 1 }
// View data outside the transaction
db.tx.find({ x: 1 }) // { x: 1 }
// Roll back the data
session.abortTransaction()

Transaction Write Mechanism

When a transaction has started, if the document the transaction wants to modify has been modified outside the transaction, the transaction triggers an Abort error when modifying this document, because the modification conflicts at this point.

In this case, the transaction needs to be aborted to update the timeline, and then the transaction restarted.

If a transaction has already started modifying a document, attempts to modify the same document outside the transaction will wait for the transaction to complete before continuing.

Notes

  • Ensure the MongoDB version is greater than 4.2 to have complete transaction capability.
  • Transactions have a default 60s timeout; timed-out transactions are automatically cancelled.
  • Shards involved in transactions cannot use arbiter nodes.
  • Transactions have extra performance overhead and affect chunk migration efficiency; chunk migration may also cause transactions to fail.
  • In multi-document transactions, the primary node must be used for reads.
  • readConcern should only be set at the transaction level, not on each individual read/write operation.

MongoDB Best Practices

Connecting to MongoDB

Provide as many node addresses as possible in the connection string — it is recommended to list them all — so nodes can be discovered more effectively to complete the cluster. Prefer domain names over IPs

  • Connecting to a replica set: mongodb://node1,node2/database?[options]
  • Connecting to a sharded cluster: mongodb://mongos1,mongos2/database?[options]

Common connection options parameters:

  • maxPoolSize: connection pool size
  • maxWaitTime: the maximum wait time for an operation; operations exceeding the time are killed
  • writeConcern: recommended to be majority to ensure data safety
  • readConcern: used when data consistency requirements are high.

Load Balancing

It is not recommended to implement load balancing in front of MongoDB; MongoDB itself has load balancing capability.

If load balancing is done in advance, the following problems may occur:

  • The driver cannot detect node liveness and cannot automatically recover from failures
  • The driver cannot determine on which node a cursor was created, leading to cursor traversal errors.

Cursors

If a cursor has been fully traversed, it closes automatically; if it has not been fully traversed, the close() method needs to be called manually, otherwise the cursor will be released after a 10-minute timeout (default value) on the server, causing unnecessary resource waste.

However, if a cursor cannot be fully traversed, it usually means the query condition is too broad, and the issue that deserves more consideration is how to tighten the condition.

Queries and Indexes

MongoDB has no resource isolation; if one operation is slow, it affects all operations.

  • Every query must have a corresponding index
  • Use Covered Indexes as much as possible
  • Use projection to reduce the content of documents returned to the client

Writes

  • Only include the fields that need updating in update statements
  • Use bulk inserts as much as possible to improve write performance
  • Use TTL to automatically expire log-type data

Document Structure

  • Prevent field names from being too long, which occupies storage space
  • Prevent nesting from being too deep; more than 2 levels becomes fairly difficult to operate
  • Do not use Chinese, punctuation marks, or other non-Latin letters as field names.

Paginated Queries

  • Avoid using count to query the total number of pages; count traverses all data and slows down queries
  • When the data volume is large, query efficiency can be improved by filtering with the id of the last element: db.posts.find({ _id: { $gt: 'the last _id of the previous page' } }).sort({ _id: 1 }).limit(20)

Transactions

  • Avoid them if possible; transactions themselves have extra overhead. Try to avoid transactions through model design
  • Do not use overly large transactions to avoid timeouts
  • When they must be used, try to keep the documents on the same shard

Sharded Cluster

Cluster Architecture

The sharding architecture divides data into different chunks, and each chunk is managed by a replica set. This can effectively separate read and write pressure, reduce load, and also allow horizontal scaling.

The architecture of a sharded cluster is as follows:

graph TB
  subgraph Business[Business Layer]
    App[Application]
    Driver[MongoDB Driver]

    App --> Driver
  end

  subgraph MongoS
    MongoS1[Primary MongoS]
    MongoS2[Backup MongoS 1]
    MongoS3[Backup MongoS 2]
  end

  subgraph Config
    Config1[Primary Config]
    Config2[Secondary Config 1]
    Config3[Secondary Config 2]
  end

  Driver --> MongoS
  Config --> MongoS
  MongoS --> Config

  subgraph Mongo1[Shard 1]
    Mongo1-master[Primary Node]
    Mongo1-slave1[Secondary Node 1]
    Mongo1-slave2[Secondary Node 2]
  end

  subgraph Mongo2[Shard 2]
    Mongo2-master[Primary Node]
    Mongo2-slave1[Secondary Node 1]
    Mongo2-slave2[Secondary Node 2]
  end

  subgraph Mongo3[Shard 3]
    Mongo3-master[Primary Node]
    Mongo3-slave1[Secondary Node 1]
    Mongo3-slave2[Secondary Node 2]
  end

  MongoS --> Mongo1
  MongoS --> Mongo2
  MongoS --> Mongo3

MongoS is the routing node, providing a unified entry to the cluster, distributing business layer requests to appropriate data nodes for operation, and merging the response results of each data node.

Since it is the entry point, to prevent cluster collapse, generally at least one backup MongoS is prepared

Config is the configuration node, providing metadata storage, mapping of shard data distribution, etc., such as data storage paths and which ranges of data different shards manage. Generally, a replica set architecture is enough.

Shards are data nodes, organized in replica set units, and can scale horizontally. There are at most 1024 data nodes, and data is not duplicated between any data nodes.

Sharding Methods

MongoDB provides three data distribution methods:

  • Range-based
  • Hash-based
  • Zone / tag-based

Range-Based

Select some fields and shard according to the field values

  • Advantages:
    • Good query performance
    • Nearby data is basically on the same node, so it is fast — for example, sharding by time and querying by time period.
  • Disadvantages:
    • Uneven data distribution
    • Prone to hotspots (one shard is frequently read and written while other shards are idle)

Hash-Based

Select some fields, hash the field values, and shard according to the hash results

  • Advantages:
    • Even data distribution
    • Write data can be spread across multiple shards, with high efficiency
  • Disadvantages:
    • Lower range query efficiency

Zone / Tag-Based

Shard according to node region and node tags

Summary

The sharding architecture can effectively solve performance problems and scaling problems, but has a large amount of extra resource overhead and is complex to manage, so do not use it unless necessary ( according to MongoDB official statistics, about 10% use sharded clusters ).

Sharding Architecture Design

Basic sharding standards:

  • The data volume should not exceed 3TB; try to keep it at 2TB per shard
  • Indexes must fit into memory

Sharding estimation:

  • Storage: total storage volume / single-server load capacity
  • Memory: working set size / (single-server memory capacity * 0.6)
  • Concurrency: total concurrency / (single-server concurrency * 0.7)

Take the maximum of the three as the number of shards

Sharding Concepts

Concepts from small to large:

  • Shard key: a field in the document, used to determine which shard the data is stored in
  • Document (doc): a row of data containing the shard key
  • Chunk: contains n documents;
  • Shard: contains n chunks
  • Cluster: contains n shards

Main Factors Affecting Sharding Performance

Sharding fields:

  • A large value range for the sharding field: too small causes data concentration and makes scaling difficult
  • Evenly distributed values of the sharding field: try to make the data of different shards evenly distributed, avoiding excessive pressure on individual shards
  • Use the shard key as a query condition: helps MongoS quickly locate which shard's data needs to be queried; otherwise MongoS needs to query every shard's data

Summary: it is generally difficult for a single shard key to satisfy all three conditions above, so a compound shard key is chosen, such as user_id + time as the sharding fields

Reads and writes:

  • Scatter writes, concentrate reads: writes should be spread across different shards as much as possible to avoid overloading the replica set master; reads should stay within the same shard as much as possible, reducing the overhead of data reading and data merging

Resources:

  • mongos and config can use lower configurations, but Shards need higher configurations
  • Since scaling also takes time, such as data migration and re-sharding, it is best to start scaling when resource load reaches 60%, to avoid affecting the business

Sharding Commands

Creating a Sharded Replica Set

Needs to be executed once on every device in the same shard

# IP to listen on
mongod --bind_ip 0.0.0.0 \
  # Shard set name
  --replSet shard1 \
  # Shard set data storage path
  --dbpath /data/shard1 \
  # Shard set log storage path
  --logpath /data/shard1/mongod.log \
  # Port to listen on
  --port 27010 \
  # Run in the background
  --fork \
  # Indicate it is a shard set node
  --shardsvr \
  # Specify the MongoDB cache size, configure as needed
  --wiredTigerCacheSizeGB 1

Initializing the Replica Set in the Shard Set

Connect to any node of the replica set via mongosh and execute the initialization; an election will automatically take place after initialization

rs.initiate({
  _id: 'shard1',
  members: [
    {
      _id: 0,
      host: 'member.he110.site:4395'
    },
    {
      _id: 1,
      host: 'member.he110.site:4396'
    },
    {
      _id: 2,
      host: 'member.he110.site:4397'
    },
  ]
})

Creating Config

Needs to be executed once on every device in the same shard

# IP to listen on
mongod --bind_ip 0.0.0.0 \
  # Shard set name
  --replSet config \
  # Shard set data storage path
  --dbpath /data/config \
  # Shard set log storage path
  --logpath /data/config/mongod.log \
  # Port to listen on
  --port 27019 \
  # Run in the background
  --fork \
  # Indicate it is a shard set config node
  configsvr \
  # Specify the MongoDB cache size, configure as needed
  --wiredTigerCacheSizeGB 1

Initializing the Replica Set in Config

Connect to any Config node via mongosh and execute the initialization; an election will automatically take place after initialization

rs.initiate({
  _id: 'config',
  members: [
    {
      _id: 0,
      host: 'config.he110.site:4395'
    },
    {
      _id: 1,
      host: 'config.he110.site:4396'
    },
    {
      _id: 2,
      host: 'config.he110.site:4397'
    },
  ]
})

Creating MongoS

Needs to be executed once on every device in the same MongoS cluster

# IP to listen on
mongos --bind_ip 0.0.0.0 \
  # Shard set log storage path
  --logpath /data/shard1/mongod.log \
  # Port to listen on
  --port 27010 \
  # Run in the background
  --fork \
  # Specify the config nodes
  --configdb config/config.he110.site:4395,config.he110.site:4396,config.he110.site:4397

Adding a Shard

Connect to the MongoS primary node via mongosh and execute the following command

sh.addShard("shard1/member.he110.site:4395,member.he110.site:4396,member.he110.site:4397")

Creating a Sharded Collection

Connect to the MongoS primary node via mongosh and execute the following commands

// Enable the sharding feature
sh.enableSharding('${db}')
// Create the sharded collection
sh.shardCollection(
  // Which collection needs sharding enabled
  '${db}.${collection}',
  // Create the shard key, supporting multiple fields
  {
    // Use id as the shard key, with hash as the sharding method
    _id: 'hashed',
  },
)

Scaling Out

Creating shards is the same as before; there is no need to create Config and MongoS again

After creation, connect to MongoS and add shard 2 via sh.addShard; MongoS will automatically complete the data migration

Operations

Performance Monitoring

Monitoring Tools

Common tools for performance monitoring include:

  • MongoDB Ops Manager: official monitoring, paid in the Enterprise edition
  • Percona
  • General-purpose server monitoring platforms
  • Program scripts

Monitoring Data Sources

  • db.serverStatus(): cumulative data since startup, the main monitoring data source
  • db.isMaster(): secondary data source
  • mongostat: a command-line tool that can only query part of the information

serverStatus() mainly focuses on the following information:

  • connections: connection count information
  • locks: lock usage
  • network: network I/O status
  • opcounters: statistics on the number of CRUD executions
  • repl: replica set configuration information
  • wiredTiger: contains WiredTiger execution information
    • block-manager: read/write status of WT data blocks
    • session: number of sessions used
    • concurrentTransactions: Ticket usage
  • mem: memory usage
  • metrics: performance metric statistics

Alert Metrics

Metric Meaning How to Obtain
opcounters
(operation counters)
Counts the number of commands such as query, update, insert, delete, getmore db.serverStatus().opcounters
tickets The number of read/write tickets of the WiredTiger storage engine, representing the number of concurrent operations that can enter the storage engine db.serverStatus().wiredTiger.concurrentTransactions
replication lag The minimum time required for a write operation to reach secondary nodes. Excessive lag reduces the value of secondary nodes and is unfavorable for operations configured with write concern ( w > 1 ) db.adminCommand({'replSetGetStatus': 1})
oplog window Represents the time range of write operations the oplog can hold; the maximum duration for a secondary node to catch up with the primary after going offline (recommended to keep above 24 hours) db.oplog.rs.find().sort({$natural:-1}).limit(1).next().ts - db.oplog.rs.find().sort({$natural:1}).limit(1).next().ts
connections Monitor the number of database connections; each connection consumes resources, so count connections during off-peak/peak periods and set alert thresholds db.serverStatus().connections
Query targeting Computes the ratio of index keys/documents scanned per second to documents returned; a high value indicates low query efficiency (possibly missing or improper indexes) var status = db.serverStatus()
status.metrics.queryExecutor.scanned / status.metrics.document.returned
status.metrics.queryExecutor.scannedObjects / status.metrics.document.returned
Scan and Order The average ratio of in-memory sort operations per second; in-memory sorting may consume a lot of resources and can be avoided through indexes var status = db.serverStatus();
status.metrics.queryExecutor.scanAndOrder / status.opcounters.query
Node status Monitor the status of each node ( PRIMARY / SECONDARY / ARBITER ); alert on abnormal status or command execution failure db.runCommand("isMaster") or db.isMaster()
dataSize Total data size of the instance (original size before compression) Execute per database: db.stats().dataSize
StorageSize The percentage of used disk space relative to total space; storage pressure needs monitoring Compute via file system tools, or estimate using db.stats().storageSize combined with total disk capacity

Backup and Recovery

The Purpose of Backup

  • Prevent data loss caused by hardware failure
  • Prevent data deletion by human error
  • Time travel
  • Regulatory requirements

Backup Mechanisms

  • Delayed node backup: set slaveDelay on a child node and exclude it from elections, using the node as a backup node
  • Full backup + oplog increment: the initial data is fully backed up; data operations can be replayed through the oplog to restore data at any point in time. To prevent the oplog from becoming too large, the initial data can be periodically updated to a newer state, trimming expired oplog

Full Backup Methods

  • Copying data files
    • The node must be shut down before copying, otherwise the copied files are invalid
    • The node can be locked for backup via db.fsyncLock() and unlocked via db.fsyncUnlock()
    • Complete the backup on a secondary node
    • Note that after a node is locked, the total number of voting nodes needs to remain odd
  • File system snapshots
    • MongoDB supports using snapshots to obtain an image of a certain moment
    • The snapshot process does not require downtime
    • Data files and journal need to be on the same volume
    • Copy and delete snapshots as soon as possible to avoid filling up the disk
  • mongodump
    • The slowest backup speed
    • Can only back up data of a certain time period
    • The oplog needs to be recorded from the start of the backup to handle dirty data problems caused by non-stop backups (data modified during the backup process)

The mongodump Command

# Start the backup and record the oplog
mongodump --host localhost:27017 --oplog

# Restore the data, and replay the oplog to ensure data consistency
mongorestore --host localhost:27017 --oplogReplay

The directory structure of a mongodump backup

  • dump: backup file directory
    • db: the backed-up database
      • collection.bson: the backed-up collection data
      • collection.metadata.bson: the backed-up collection metadata
    • oplog.bson: the oplog during the backup period

Security Architecture

Authentication

User authentication:

Authentication method Description Notes
Username + password The default authentication method, using the SCRAM-SHA-1 hash algorithm; user information is stored in MongoDB's local database None
Certificate-based Based on the X.509 standard: the server needs to provide a certificate file at startup, the client needs a certificate file to connect, and certificates issued by internal or external CAs are supported None
LDAP external authentication Connect to an external LDAP server for authentication Enterprise feature
Kerberos external authentication Connect to an external Kerberos server for authentication Enterprise feature

Cluster node authentication:

  • Keyfile: copy the same Keyfile to different nodes
  • X.509: certificate-based authentication; different certificates for different nodes are recommended

Creating an administrator account:

// Switch to the admin collection
use admin;

// Create the user admin with password 123456, with permissions to manage all users, databases, and read-write
db.createUser({
  user: "admin",
  pwd: "123456",
  roles: [
    { role: "userAdminAnyDatabase", db: "admin" },
    { role: "dbAdminAnyDatabase", db: "admin" },
    { role: "readWriteAnyDatabase", db: "admin" }
  ]
})

Logging in with the account

mongosh \
  # Username, can be abbreviated as -u
  --username admin \
  # Password, can be abbreviated as -p
  --password 123456 \
  # The database to operate on
  --authenticationDatabase admin

Authorization

Role-based access control: get role information via db.getRole('role name'), and get role and privilege information via db.getRole('role name', { showPrivileges: true }); privileges are stored in the privileges.actions field

graph TD
  classDef app fill:#ffd6e7,stroke:#ff69b4
  classDef db fill:#e6f4ff,stroke:#1890ff
  classDef backup fill:#b3e0ff,stroke:#096dd9
  classDef root fill:#f0f0f0,stroke:#666

  %% Permission relationship diagram
  root("root<br>(superuser role)")

  subgraph Global Administration
    readAnyDB["readAnyDatabase"]
    writeAnyDB["readWriteAnyDatabase"]
    adminAnyDB["dbAdminAnyDatabase"]
    userAnyDB["userAdminAnyDatabase"]
  end

  root --> readAnyDB["readAnyDatabase"]
  root --> writeAnyDB["readWriteAnyDatabase"]
  root --> adminAnyDB["dbAdminAnyDatabase"]
  root --> userAnyDB["userAdminAnyDatabase"]

  subgraph Backup & Restore
    backup
    restore
  end
  root --> restore

  subgraph Database Administration
    dbOwner --> dbAdmin
    dbOwner --> userAdmin
  end

  subgraph Cluster Administration
    ClusterAdmin --> hostManager
    ClusterAdmin --> clusterManager
    ClusterAdmin --> clusterMonitor
  end
  root --> ClusterAdmin

  subgraph Application User
    readWrite --> read
  end

  %% Permission inheritance relationships
  readAnyDB --> read
  writeAnyDB --> readWrite
  adminAnyDB --> dbAdmin
  userAnyDB --> userAdmin

  %% Style bindings
  class root root
  class GlobalAdmin,readAnyDB,writeAnyDB,adminAnyDB,userAnyDB db
  class BackupRestore,backup,restore backup
  class DBAdmin,dbAdmin,userAdmin,dbOwner db
  class ClusterAdmin,hostManager,clusterManager,clusterMonitor db
  class AppUser,read,readWrite app

Creating a role:

db.createRole({
  role: 'sampleRole',
  // Declare the privileges the role has
  privileges: [
    {
      // Allow operating on test.sample
      resource: {
        db: 'test',
        collection: 'sample'
      },
      // Allow updates
      actions: ['update']
    },
  ],
  // Inherit privileges from other roles
  roles: [
    // Inherit the read privilege of the read role, allowing reads on the test database
    {
      role: 'read',
      db: 'test',
    },
  ],
})

Encryption

  • Communication encryption: MongoDB allows encrypting all network transmission via TLS/SSL, including between client and server, and within replica sets
  • Storage encryption: Enterprise MongoDB supports encrypting data when persisting to disk; the specific process is as follows
    • Generate a master key, used to generate the key for each database
    • The key of each database is used to encrypt the database
    • Based on the generated database key, encrypt the data in the database
    • For management, you only need to focus on the master key; the database keys are stored inside the database
  • Field encryption: individual fields can be encrypted with their own keys; only ciphertext can be seen in the database, and the whole process is operated automatically by MongoDB. The specific process is as follows
    1. Request the MongoDB driver to encrypt the field
    2. The MongoDB driver queries the key manager
    3. The key manager returns the key
    4. The MongoDB driver queries the database
    5. The database returns the encrypted data
    6. Decrypt the data with the key and return it to the user

Auditing

The auditing feature can record specific behaviors; it requires purchasing the Enterprise edition

  • Format: json
  • Storage: local file or syslog
  • Content:
    • Schema change ( DDL )
    • CRUD (DML)
    • User authentication

# IP to listen on
mongod --bind_ip 0.0.0.0 \
  # Shard set data storage path
  --dbpath /data/config \
  # Shard set log storage path
  --logpath /data/config/mongod.log \
  # Port to listen on
  --port 27019 \
  # Run in the background
  --fork \
  # Audit log recorded to syslog; another optional value is file
  --auditDestination syslog \
  # The format of the stored audit file; not needed when configured as syslog
  --auditFormat JSON \
  # Configure the storage path of the audit file; not needed when configured as syslog
  --auditPath /data/audit \
  # The content of the audit file; here auditing collection creation and deletion
  --auditFilter '{ atype: { $in: [ "createCollection", "dropCollection" ] } }'

Best Practices

By default, MongoDB has no security settings and no authorization; users need to be created manually and access closed off

  • Enable authentication
  • Strict permission control
  • Encrypted transmission, encrypted data, activity auditing
  • Deploy servers on the intranet, deploy firewalls, disable http access, disable restful access, bind IPs, replace default ports, etc.
  • Follow local compliance requirements

Index Mechanism

Glossary

  • Index: usually a field of a record.

  • Key: key name. All fields of each record are key names.

  • Data Page: where records are stored.

  • Covered Query: means all queried fields are in the index, and no data needs to be loaded from data pages.

  • FETCH: fetching. Refers to querying the corresponding record in the data pages according to the index.

  • IXSCAN: index scan. Scanning the index.

  • COLLSCAN: collection scan. Scanning the data pages.

  • Query Shape: the data structure of the query conditions. Different query conditions also affect query performance.

  • Index Prefix: when multiple key names are indexed, all query prefixes are indexed without needing additional indexes. For example, { a: 1, b: 1, c: 1 } automatically adds the indexes { a: 1, b: 1 } and { a: 1 }

  • Selectivity: preferentially use conditions that can filter out the vast majority of data for queries.

Index Storage Structure

Index storage uses B- trees; generally a B tree is a binary tree, while a B- tree can be a multi-way tree

Index Execution

When query conditions hit multiple indexes, a few records are queried via multiple threads and whichever is faster is used, so a slow index may be used (an index corresponding to more data)

graph TB
  classDef orange fill:#FFA500,stroke:#FF4500,color:white
  classDef green fill:#98FB98,stroke:#32CD32
  classDef diamond fill:#FFD700,stroke:#DAA520

  %% Flow nodes
  start[("Query statement")]:::orange --> match{Match plan cache}

  %% Match-success branch
  match -- Y --> evaluate{Evaluate plan performance}:::diamond
  evaluate -- Y --> execute["Execute according to the execution plan"]:::green
  execute --> result["Return results"]:::green

  %% Evaluation-failure branch
  evaluate -- N --> evict1["Evict the plan cache"]:::green
  evict1 --> generate1["Generate candidate plans"]:::green
  generate1 --> assess1["Evaluate candidate plans"]:::green
  assess1 --> select1["Select the optimal plan"]:::green
  select1 --> create1["Create the plan cache"]:::green
  create1 --> execute

  %% Match-failure branch
  match -- N --> generate2["Generate candidate plans"]:::green
  generate2 --> assess2["Evaluate candidate plans"]:::green
  assess2 --> select2["Select the optimal plan"]:::green
  select2 --> create2["Create the plan cache"]:::green
  create2 --> execute

Viewing the Execution Process

You can append explain after the query statement to request viewing the retrieval process: db.col.find({ name: '张三' }).explain(true)

Focus on:

  • executionStages.stage: whether it is COLLSCAN or IXSCAN, i.e., whether an index is used
  • totalDocsExamined and nReturned: how many documents were examined to return nReturned results
  • totalKeysExamined: how many keys were examined
  • executionTimeMillis: how many milliseconds it took to execute

Query conditions should follow the ESR principle, arranging index fields in the order: exact match > sort condition > range match; if a field has no index, put it at the very end

Index Types

  • Single-key index
  • Compound index
  • Multi-key index
  • Geospatial index
  • Full-text index
  • TTL index
  • Partial index
  • Hash index

Read/Write Performance

Driver Layer

The overall driver workflow diagram is as follows:

MongoDB driver workflow

Selecting Nodes

For replica set read operations, which nodes are selected is determined by readPreference. If you do not want distant nodes to be selected, you should do one of the following:

  • Set them as hidden nodes
  • Control candidate nodes via Tags
  • Use nearest to select nearby nodes

Queuing and Waiting

When the total number of connections exceeds the maximum number of connections ( maxPoolSize ), queuing and waiting occurs, i.e., blocking.

The following approaches can be used to solve it:

  • Increase the maximum number of connections (limited by server performance, may not always help)
  • Optimize operation performance and shorten operation time

Connection and Authentication

Authentication consumes somewhat more time than no authentication, but authentication ensures security. To optimize connection speed, the following approaches can be used:

  • Set the minimum number of connections ( minPoolSize ), keeping a certain number of idle connections in the connection pool that can be reused directly without extra creation
  • During business processing, avoid sudden large bursts of requests

Database Layer

The overall database layer workflow diagram is as follows:

MongoDB database workflow

Queuing and Waiting

Insufficient tickets cause database queries to queue, which is generally a problem of other operations being slow. Solutions are as follows:

  • Use performance monitoring to find slow commands, use explain to troubleshoot problems, and optimize operation performance
  • zlib compression itself also takes time; performance slowness may be caused by compression being enabled

Read Requests

The main reason for slow read requests is indexes. You can refer to the index working process mentioned in the Index Execution section, troubleshoot problems with explain, and optimize indexes.

The process of a MongoDB read request loading resources, searching, and sorting.

Write Operations

Write operations also need to use indexes to find the location to modify. If write operations are slow, you can also locate the problem with explain to see whether it is caused by index problems.

The process of a MongoDB write request writing to the Journal, cache, oplog, indexes, and data after locating the data.

Writing data needs to use the WT cache, which becomes invalid after being written to disk. If disk I/O performance is low and the cache cannot be consumed in time, write operations will also be slowed down.

MongoDB data is first written to the WT cache, then written to disk via I/O.

Merging Sharded Results

  • Avoid sorting operations in MongoDB to reduce database query time
  • Use query conditions with the shard key as much as possible, specifying which shard to query instead of querying them all
The result sets of three MongoDB shards are merged into mongos and returned to the application.

Network Transmission

  • Network latency
  • Enabling TLS/SSL between clusters also affects transmission performance

Performance Diagnosis

mongostat

Counts which operations are happening at the same time; focus on the following fields:

  • dirty: the proportion of data that has been updated in memory but not persisted. Below 5% is normal; above 5% MongoDB accelerates writes, and above 20% new requests are blocked so it can write to disk at full power.
  • used: how much of the memory allocated to MongoDB is used. Below 80% is normal; above 80% MongoDB accelerates memory cleanup, and above 95% new requests are blocked so it can free up memory space at full power.
  • qrw / arw: queued requests.
  • conn: the number of connections.

mongotop

A tool for understanding the pressure status of collections.

It can query the total time / read time / write time of each collection.

mongod Logs

mongod logs record queries that take more than 100ms to execute, along with the query plan.

mtools

An open-source Python tool, installed via pip install mtools. Common commands are as follows:

Common Database Cluster Design Solutions

Two-Site Three-Center Cluster Design

Disaster Recovery Levels

Level Disaster recovery strategy RPO (Recovery Point Objective) RTO (Recovery Time Objective) Implementation and notes
L0 No backup center 24 hours 4 hours Local data backup only, no disaster recovery capability
L1 Local backup + off-site storage 24 hours 8 hours Back up critical data locally and then send it off-site for storage; systems and data must be restored according to predetermined procedures
L2 Dual-center primary-backup mode Seconds Minutes to half an hour Set up a hot backup site off-site and back up data over the network; the backup site takes over the primary site's business in a disaster
L3 Dual-center active-active Seconds Seconds Set up mutually backing-up data centers in two locations; when either center fails, the other takes over the work
L4 Dual-center active-active + remote hot backup (two sites, three centers) Seconds Minutes Same-city active-active centers + remote hot backup; when both same-city centers are unavailable at the same time, quickly switch to the remote center

Conventional Two-Site Three-Center Solution

Network Layer Design

The DNS server needs to use GSLB global load balancing (with DNS functionality and health check functionality); after a center goes down, GSLB automatically switches to another center.

Application Layer Design
  • Use load balancing and virtual IPs
  • Use the same Session
  • Use the same set of designs
Database Design

Mainly consider cross-center synchronization problems

  • DBMS adopts a log-based synchronization solution
  • The file system adopts a storage-mirroring-based synchronization solution

MongoDB Two-Site Three-Center Solution

The three centers are: two same-city centers + one remote center

Both same-city centers need dual nodes, and the remote center has a single node — that is, at least 5 nodes.

The primary data center has the highest priority, followed by the same-city backup center, and then the cross-city center.

writeConcern needs to use majority.

The two same-city centers need to use a low-latency dedicated line to reduce data synchronization latency and avoid data loss.

The complete setup steps are as follows:

  • Configure domain name resolution
  • Install MongoDB
  • Configure the replica set
  • Configure priorities
// Get the replica set configuration
cfg = rs.conf()
// Modify the priority of the first node; the default is 1
cfg.members[0].priority = 10
// Let the replica set update the configuration information
rs.reconfig(cfg)

Global Multi-Write Cluster ( global cluster )

Business requirement: multinational enterprises experience high read/write latency across regions, with latency approaching the second level

The solution is as follows:

Use multiple sharded clusters. In each sharded cluster, the local center has two replica set nodes, and each remote center has one replica set node.

Local data is read and written in the local center; remote data can only be read, and writes need to go through the remote data center.

The complete setup steps are as follows:

  • For the data collections to be sharded, add a region-distinguishing field; anything that can distinguish the data works
  • Tag each shard of the cluster with a region label
  • Specify the shard chunk range belonging to each region
// Tag the shard shard0
sh.addShardTag('shard0', 'Asia')

// Specify the data range for the shard
sh.addTagRange(
  // database.collection
  'crm.orders',
  // The shard range
  { locationCode: 'CN', order_id: 'min' },
  { locationCode: 'CN', order_id: 'max' },
  // Which tag to add to the data
  'Asia'
)

Tags help MongoS quickly select shards, reducing cross-center reads and writes.

Go-Live Preparation

Before Go-Live

  • Performance testing: stress-test various metrics such as CRUD counts, connection counts, etc., set up monitoring ranges, and adjust hardware resources
  • Environment check: MongoDB provides a pre-go-live environment check checklist, which you can follow for inspection

After Go-Live

  • Performance monitoring
  • Regular inspections

Version Upgrades

Single-Machine Upgrade

The complete single-machine upgrade process is as follows:

MongoDB single-machine upgrade steps: compatibility check, driver upgrade, local testing, and online upgrade.

Replica Set Upgrade

A replica set upgrade generally upgrades nodes one by one; the node being upgraded can be regarded as down, but the replica set can continue working. Reference upgrade steps are as follows:

Steps for upgrading MongoDB secondary nodes one by one, switching primary-secondary, upgrading the original primary, and adjusting the FCV.

Downgrade

If the upgrade fails for any reason, you need to downgrade to the original old version. During the downgrade process:

  • During a rolling downgrade, the cluster can stay online; only a certain amount of non-writable time occurs when switching nodes
  • Before downgrading, new-version features that have been used should be removed first. For example, if NumberDecimal is used, all documents using NumberDecimal should have that field removed first
  • Setting the FCV ( Feature Compatibility Version ) can make it functionally compatible with the old version
  • After the FCV is set, roll-replace with the old version