ITcompressor IT — compressed and to the point
← Back to articles

MongoDB

MongoDB is a document-oriented database: data is stored not in tables but in collections of JSON-like documents (BSON). Flexible schema, a rich query language and built-in replication.

Installation#

The official repository for Ubuntu 22.04 (MongoDB 7.0):

# Repository signing key (apt-key is deprecated — put the key into a keyring)
sudo apt install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
  sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor

# Repository
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

# Install and start
sudo apt update
sudo apt install -y mongodb-org
sudo systemctl enable --now mongod

Connecting to the shell (in modern versions it is mongosh; the legacy mongo client has been removed):

mongosh

Everything below is shell commands (JavaScript). We use the customers collection with documents shaped like { name, email, city, age, status }.

Inserting#

// One document
db.customers.insertOne({
  name: "Olga Smirnova",
  email: "olga@example.com",
  city: "Minsk",
  age: 29,
  status: "active"
})

// An array of documents — batch insert
db.customers.insertMany([
  { name: "Ivan Petrov",  email: "ivan@example.com", city: "Gomel", age: 34, status: "inactive" },
  { name: "Anna Koval",   email: "anna@example.com", city: "Minsk", age: 41, status: "active" },
  { name: "Maksim Lee",   email: "maks@example.com", city: "Brest", age: 23, status: "active" }
])

The collection is created automatically on the first insert; for a document without an explicit _id, MongoDB generates one itself.

Queries#

Simple ones#

// All documents in the collection
db.customers.find()

// The first matching document
db.customers.findOne({ name: "Anna Koval" })

// Exact field value
db.customers.find({ city: "Minsk" })

Comparison operators#

// Age greater than 25
db.customers.find({ age: { $gt: 25 } })

// Combined range: 25 <= age <= 40
db.customers.find({ age: { $gte: 25, $lte: 40 } })

// Membership in a set of values
db.customers.find({ city: { $in: ["Minsk", "Brest"] } })

// Field exists / does not exist
db.customers.find({ phone: { $exists: true } })

// Substring at the beginning of a field (regular expression)
db.customers.find({ name: { $regex: "^A" } })

Core comparison operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $regex.

Logical operators#

// AND: both conditions at once
db.customers.find({
  age: { $gte: 25 },
  status: "active"
})

// Explicit form with $and (needed when a single-field condition is complex)
db.customers.find({
  $and: [
    { age: { $gte: 25 } },
    { age: { $lte: 40 } }
  ]
})

// OR: at least one of the conditions
db.customers.find({
  $or: [
    { city: "Minsk" },
    { age: { $gte: 35 } }
  ]
})

// Negation of a set of conditions
db.customers.find({ $nor: [ { status: "inactive" }, { age: { $lt: 25 } } ] })

Projections, sorting and pagination#

// Only name and email (without _id) for active customers
db.customers.find(
  { status: "active" },
  { name: 1, email: 1, _id: 0 }
)

// Newest first, 10 documents, third page
db.customers.find()
  .sort({ age: -1 })
  .limit(10)
  .skip(20)

Aggregations#

A document pipeline: every stage ($match, $group, $sort, …) processes the stream and passes the result to the next one.

// Group by city: count and average age
db.customers.aggregate([
  { $match: { status: "active" } },
  { $group: { _id: "$city", count: { $sum: 1 }, avgAge: { $avg: "$age" } } },
  { $sort: { count: -1 } },
  { $limit: 5 }
])

Updates#

// Update one document: replace fields via $set
db.customers.updateOne(
  { name: "Ivan Petrov" },
  { $set: { status: "active" } }
)

// Update all documents matching the filter
db.customers.updateMany(
  { status: "inactive" },
  { $set: { city: "unknown" } }
)

// Increment a counter
db.customers.updateOne(
  { name: "Anna Koval" },
  { $inc: { logins: 1 } }
)

// Add an element to an array
db.customers.updateOne(
  { name: "Anna Koval" },
  { $push: { tags: "vip" } }
)

// Remove a field entirely
db.customers.updateOne(
  { name: "Anna Koval" },
  { $unset: { phone: "" } }
)

Without $set and other operators, updateOne({ … }, { age: 30 }) replaces the whole document with a new one. Always specify an operator.

A fairly common pattern is upsert: update, and if the document does not exist, insert it (handy for incremental syncs):

db.customers.updateOne(
  { email: "new@example.com" },
  { $set: { name: "New User", status: "active" } },
  { upsert: true }
)

Deletions#

// One document (the first match)
db.customers.deleteOne({ email: "maks@example.com" })

// All documents matching the filter
db.customers.deleteMany({ status: "inactive" })

// Drop the collection along with its indexes
db.customers.drop()

deleteMany({}) clears documents, drop() removes the whole collection (including indexes). For "wiping a table" drop() is the faster option.

Replica set#

Replication keeps copies of the data on several nodes: a single PRIMARY accepts writes while the rest sync with it via the oplog. When the PRIMARY goes down, the members elect a new one and the cluster keeps working.

Node configuration#

The same replSetName in /etc/mongod.conf on every node. Note that bindIp and port live in the net section, not in replication:

# /etc/mongod.conf
net:
  bindIp: 0.0.0.0
  port: 27017
replication:
  replSetName: rs0

Then on every node: sudo systemctl restart mongod. The nodes need access to each other and to clients on port 27017:

# Tight: only from a trusted subnet
sudo ufw allow from 10.0.0.0/24 to any port 27017

Initialization#

Connect to one node and initialize the set — the rest are listed in members:

// Connection (the replica set is set in the connection string)
mongosh "mongodb://mongo1.example.com:27017/?replicaSet=rs0"
// Runs once, on any listed node
rs.initiate({
  _id: "rs0",
  members: [
    { _id: 0, host: "mongo1.example.com:27017" },
    { _id: 1, host: "mongo2.example.com:27017" },
    { _id: 2, host: "mongo3.example.com:27017" }
  ]
})

Status and configuration#

// State of the members: health, state (PRIMARY/SECONDARY), lag
rs.status()

// Current configuration of the set
rs.conf()

// Hand the PRIMARY role to another node (e.g., before maintenance)
rs.stepDown()

Adding and removing members#

// Attach a new node (first set the same replSetName in mongod.conf)
rs.add("mongo4.example.com:27017")

// Remove a node from the set
rs.remove("mongo3.example.com:27017")

A member's priority is set in the set configuration: rs.conf().members — the priority field; the node with the highest priority wins the PRIMARY election. A replica set needs at least three data-bearing nodes (or arbiters) — the quorum must be reliable.

← Back to articles