Stage 3 · Build
NoSQL & Distributed Databases
Document Stores (MongoDB)
Document model, aggregation pipeline, indexes, and Atlas operational patterns.
Document Model
MongoDB stores data as BSON (Binary JSON) documents in collections. Each document can have a different structure — no fixed schema is required. Documents are nested, allowing complex hierarchical data without JOINs.
// User document with embedded orders
{
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "Alice Chen",
email: "alice@example.com",
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA"
},
orders: [
{ product: "Widget", quantity: 5, price: 9.99, date: ISODate("2024-06-15") },
{ product: "Gadget", quantity: 2, price: 24.99, date: ISODate("2024-06-20") }
],
tags: ["premium", "early-adopter"],
created_at: ISODate("2024-01-01T00:00:00Z")
}The document model embeds related data together. Orders are embedded in the user document, eliminating the need for JOINs. This is optimal when data is read together and has a natural containment relationship.
CRUD Operations
// Insert
db.users.insertOne({
name: "Bob Smith",
email: "bob@example.com",
created_at: new Date()
});
// Read
db.users.find({ "address.city": "San Francisco" });
db.users.findOne({ _id: ObjectId("507f1f77bcf86cd799439011") });
// Update
db.users.updateOne(
{ _id: ObjectId("507f1f77bcf86cd799439011") },
{ $push: { orders: { product: "New Item", quantity: 1, price: 49.99 } } }
);
// Delete
db.users.deleteOne({ _id: ObjectId("507f1f77bcf86cd799439011") });MongoDB operations are document-oriented. Updates use atomic operators like $push, $set, $inc that modify specific fields without replacing the entire document.
Aggregation Pipeline
// Revenue by month (similar to SQL GROUP BY)
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: {
_id: { $dateToString: { format: "%Y-%m", date: "$created_at" } },
total_revenue: { $sum: "$total" },
order_count: { $sum: 1 }
}},
{ $sort: { _id: 1 } }
]);
// Top customers by order count (with $lookup — equivalent to JOIN)
db.users.aggregate([
{ $lookup: {
from: "orders",
localField: "_id",
foreignField: "user_id",
as: "orders"
}},
{ $project: {
name: 1,
order_count: { $size: "$orders" }
}},
{ $sort: { order_count: -1 } },
{ $limit: 10 }
]);The aggregation pipeline processes documents through a sequence of stages. Each stage transforms the data and passes it to the next. $match filters, $group aggregates, $lookup joins, $project shapes output.
Place $match as early as possible in the pipeline to reduce the data processed by subsequent stages. $match can use indexes, while most other stages cannot.
Indexing in MongoDB
// Single field index
db.users.createIndex({ email: 1 }, { unique: true });
// Compound index (column order matters)
db.orders.createIndex({ user_id: 1, created_at: -1 });
// Multikey index (automatically indexes arrays)
db.users.createIndex({ tags: 1 });
// Text index for full-text search
db.products.createIndex({ name: "text", description: "text" });
// TTL index (auto-delete documents after time)
db.sessions.createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 });
// Geospatial index
db.stores.createIndex({ location: "2dsphere" });
// Check index usage
db.users.aggregate([{ $indexStats: {} }]);MongoDB indexes work similarly to PostgreSQL B-tree indexes. Compound indexes follow the same leftmost prefix rule. TTL indexes automatically delete expired documents — useful for sessions and cache.
Schema Design Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Embedding | Related data inside parent document | 1:1 or 1:few relationships |
| Referencing | ObjectId reference to another collection | 1:many or many:many |
| Subset | Embedded data + referenced overflow | Frequently + rarely accessed data |
| Bucket | Time-series data grouped into chunks | IoT, metrics, events |
// Pattern 1: Embedding (denormalization)
// Good for: user profile with address
{
name: "Alice",
address: { street: "123 Main", city: "SF" } // embedded
}
// Pattern 2: Referencing (normalization)
// Good for: users with thousands of orders
{
_id: ObjectId("..."),
name: "Alice"
// orders are in separate collection, referenced by user_id
}
// Pattern 3: Bucket pattern (time-series)
// Good for: hourly metrics
{
sensor_id: "temp_01",
date: ISODate("2024-06-15"),
measurements: [
{ time: ISODate("2024-06-15T00:00:00"), value: 72.5 },
{ time: ISODate("2024-06-15T00:01:00"), value: 72.8 },
// ... up to 1440 measurements per day
]
}Choose embedding when data is read together. Choose referencing when the embedded collection would grow unbounded (thousands of elements). The bucket pattern groups time-series data to reduce document count.
Operational Patterns
- Use MongoDB Atlas for managed operations — avoids replica set and sharded cluster management
- Enable authentication and TLS — MongoDB defaults to no auth
- Set up oplog tailing for change streams — real-time data synchronization
- Monitor connections and operation latency via Atlas or serverStatus
- Use read preferences to distribute reads across replicas
A fresh MongoDB installation has no authentication enabled and binds to all interfaces. Always enable auth, TLS, and firewall access before deploying to production.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.