| name | mongodb-architect |
| description | MongoDB architecture and design mastery covering document schema design, advanced indexing strategies, aggregation pipeline patterns, sharding architecture, replica sets, multi-document transactions, change streams, Atlas cloud features, performance profiling, schema validation, and embedding vs referencing trade-offs.
Use when the user asks about mongodb architect, mongodb architect best practices, or needs guidance on mongodb architect implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"database sql backend","category":"backend-systems","subcategory":"database","depends":"","disclaimer":"none","difficulty":"intermediate"} |
MongoDB Architect
Core Philosophy
MongoDB schema design is application-driven, not data-driven. Design your documents around your access patterns, not around entity relationships. The cardinal rule: data that is accessed together should be stored together. Every schema decision is a trade-off between read performance, write performance, data consistency, and storage efficiency.
Document Schema Design Patterns
Pattern 1: Embedding (Denormalization)
Embed related data directly within a document when:
- The embedded data has a 1:1 or 1:few relationship
- The embedded data is always retrieved with the parent
- The embedded data does not grow unboundedly
{
_id: ObjectId("..."),
name: "Jane Smith",
email: "jane@example.com",
addresses: [
{ type: "home", street: "123 Main St", city: "Portland", state: "OR", zip: "97201" },
{ type: "work", street: "456 Oak Ave", city: "Portland", state: "OR", zip: "97204" }
]
}
Pattern 2: Referencing (Normalization)
Reference separate documents when:
- The related data has a 1:many or many:many relationship
- The related data is large or grows unboundedly
- The related data is frequently updated independently
{
_id: ObjectId("..."),
user_id: ObjectId("user_abc"),
items: [
{ product_id: ObjectId("prod_1"), quantity: 2, price: 29.99 },
{ product_id: ObjectId("prod_2"), quantity: 1, price: 49.99 }
],
total: 109.97,
created_at: ISODate("2025-03-15T10:30:00Z")
}
Pattern 3: Bucket Pattern (Time-Series Optimization)
Group related time-series documents into buckets to reduce document count and index size.
{
sensor_id: "temp-sensor-42",
bucket_start: ISODate("2025-03-15T14:00:00Z"),
bucket_end: ISODate("2025-03-15T14:59:59Z"),
count: 60,
measurements: [
{ ts: ISODate("2025-03-15T14:00:00Z"), value: 22.5 },
{ ts: ISODate("2025-03-15T14:01:00Z"), value: 22.6 },
],
summary: { min: 22.1, max: 23.4, avg: 22.7 }
}
Pattern 4: Computed Pattern
Pre-compute frequently accessed aggregations to avoid repeated calculations.
{
_id: ObjectId("..."),
name: "Wireless Mouse",
review_stats: {
count: 1247,
average: 4.3,
distribution: { 1: 42, 2: 67, 3: 134, 4: 389, 5: 615 }
}
}
db.products.updateOne(
{ _id: productId },
{
$inc: { "review_stats.count": 1, [`review_stats.distribution.${rating}`]: 1 },
$set: { "review_stats.average": newAverage }
}
);
Pattern 5: Outlier Pattern
Handle documents with outlier characteristics differently.
{ _id: "book1", title: "Normal Book", reviews: [ ], has_overflow: false }
{ _id: "book2", title: "Popular Book", reviews: [ ], has_overflow: true }
{ _id: "book2_overflow_1", parent_id: "book2", reviews: [ ] }
Embedding vs Referencing Decision Tree
Will the related data grow unboundedly?
YES -> Reference
NO -> Is the related data > 16MB (document size limit)?
YES -> Reference
NO -> Is the related data updated independently and frequently?
YES -> Reference (to avoid rewriting entire parent)
NO -> Is the related data always accessed with the parent?
YES -> Embed
NO -> Consider both; benchmark with real queries
Indexing Strategies
Index Types
db.users.createIndex({ email: 1 }, { unique: true });
db.orders.createIndex({ customer_id: 1, created_at: -1 });
db.articles.createIndex({ tags: 1 });
db.articles.createIndex({ title: "text", body: "text" },
{ weights: { title: 10, body: 1 } });
db.products.createIndex({ "attributes.$**": 1 });
db.stores.createIndex({ location: "2dsphere" });
db.users.createIndex({ user_id: "hashed" });
db.orders.createIndex(
{ status: 1, : - },
{ : { : { : [, ] } } }
);
db..({ : }, { : });
ESR Rule for Compound Indexes
Order compound index fields by: Equality, Sort, Range
db.orders.find({
customer_id: "cust_123",
total: { $gte: 50, $lte: 500 }
}).sort({ created_at: -1 });
db.orders.createIndex({ customer_id: 1, created_at: -1, total: 1 });
Index Analysis
db.orders.aggregate([
{ $indexStats: {} }
]);
db.orders.find({ customer_id: "cust_123" })
.sort({ created_at: -1 })
.explain("executionStats");
Aggregation Pipeline
Pipeline Stages and Optimization
db.orders.aggregate([
{ $match: {
created_at: { $gte: ISODate("2025-01-01"), $lt: ISODate("2025-04-01") },
status: "completed"
}},
{ $unwind: "$items" },
{ $lookup: {
from: "products",
localField: "items.product_id",
foreignField: "_id",
as: "product",
pipeline: [{ $project: { name: 1, category: 1 } }]
}},
{ $unwind: "$product" },
{ $group: {
_id: "$product.category",
total_revenue: { $sum: { $multiply: ["$items.quantity", "$items.price"] } },
total_orders: { $sum: 1 },
avg_order_value: { $avg: { $multiply: ["$items.quantity", ] } }
}},
{ : { : - } },
{ : {
: ,
: { : [, ] },
: ,
: { : [, ] },
:
}}
]);
Pipeline Optimization Rules
- Place
$match and $limit as early as possible
- Place
$project before $group to reduce document size
- Use
$lookup with pipeline sub-queries to filter joined documents
- Avoid
$unwind on large arrays when possible -- use $filter or array operators
- Use
{ allowDiskUse: true } for pipelines exceeding 100MB memory limit
Window Functions (MongoDB 5.0+)
db.sales.aggregate([
{ $setWindowFields: {
partitionBy: "$region",
sortBy: { date: 1 },
output: {
running_total: { $sum: "$amount", window: { documents: ["unbounded", "current"] } },
moving_avg_7d: { $avg: "$amount", window: { range: [-7, 0], unit: "day" } },
rank: { $rank: {} }
}
}}
]);
Sharding Architecture
Shard Key Selection
shell-cmd.shardCollection("mydb.orders", { customer_id: "hashed" });
shell-cmd.shardCollection("mydb.events", { tenant_id: 1, created_at: 1 });
shell-cmd.addShardToZone("shard-us-east", "US");
shell-cmd.addShardToZone("shard-eu-west", "EU");
shell-cmd.updateZoneKeyRange("mydb.users", { region: "US" }, { region: "US~" }, "US");
shell-cmd.updateZoneKeyRange("mydb.users", { region: "EU" }, { region: "EU~" }, "EU");
Shard Key Decision Criteria
| Criterion | Hashed | Ranged | Compound |
|---|
| Even distribution | Excellent | Depends | Good |
| Range queries | No | Yes | Yes |
| Query isolation | With equality | Yes | Yes |
| Write scaling | Excellent | Risk of hotspot | Good |
Replica Sets
rs.initiate({
_id: "myReplicaSet",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 },
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", priority: 1 },
{ _id: 3, host: "mongo4:27017", priority: 0, hidden: true, slaveDelay: 3600 }
]
});
db.orders.find({}).readPref("secondaryPreferred", [{ dc: "east" }]);
Multi-Document Transactions
const session = client.startSession();
try {
session.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" },
readPreference: "primary"
});
const accounts = session.getDatabase("bank").collection("accounts");
await accounts.updateOne({ _id: fromAccount }, { $inc: { balance: -amount } }, { session });
await accounts.updateOne({ _id: toAccount }, { $inc: { balance: amount } }, { session });
const ledger = session.getDatabase("bank").collection("ledger");
await ledger.insertOne({
from: fromAccount, to: toAccount, amount, timestamp: new Date()
}, { session });
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
Transaction best practices:
- Keep transactions short (< 60 seconds)
- Limit to 1000 documents modified per transaction
- Design schema to minimize need for transactions (embedding > referencing when atomicity is needed)
- Use retryable writes for transient errors
Change Streams
const pipeline = [
{ $match: { "fullDocument.status": "urgent", operationType: { $in: ["insert", "update"] } } },
{ $project: { fullDocument: 1, operationType: 1, updateDescription: 1 } }
];
const changeStream = db.collection("tickets").watch(pipeline, {
fullDocument: "updateLookup",
resumeAfter: savedResumeToken
});
changeStream.on("change", (event) => {
console.log(`${event.operationType}: ${JSON.stringify(event.fullDocument)}`);
saveResumeToken(event._id);
});
Schema Validation
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["email", "name", "role"],
properties: {
email: {
bsonType: "string",
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
description: "Must be a valid email address"
},
name: { bsonType: "string", minLength: 1, maxLength: 200 },
role: { enum: ["admin", "editor", "viewer"] },
age: { bsonType: "int", minimum: 0, maximum: 150 },
addresses: {
bsonType: "array",
maxItems: 10,
items: {
bsonType: "object",
required: ["street", "city"],
properties: {
street: { bsonType: "string" },
: { : },
: { : , : }
}
}
}
}
}
},
: ,
:
});
Performance Profiling
db.setProfilingLevel(1, { slowms: 100 });
db.system.profile.find({
millis: { $gt: 100 },
ns: "mydb.orders"
}).sort({ ts: -1 }).limit(10);
db.currentOp({ "active": true, "secs_running": { $gt: 5 } });
db.killOp(opId);
Atlas Features
Atlas Search (Lucene-based)
db.products.aggregate([
{ $search: {
index: "product_search",
compound: {
must: [{ text: { query: "wireless mouse", path: ["name", "description"] } }],
filter: [{ range: { path: "price", gte: 10, lte: 100 } }],
should: [{ text: { query: "ergonomic", path: "features", score: { boost: { value: 2 } } } }]
},
highlight: { path: ["name", "description"] }
}},
{ $project: { name: 1, price: 1, score: { $meta: "searchScore" }, highlights: { $meta: "searchHighlights" } } },
{ $limit: 20 }
]);
Atlas Data Federation, Online Archive, and Charts
- Data Federation: Query across Atlas clusters, S3, and HTTP sources with a unified interface
- Online Archive: Automatically tier cold data to cheaper storage while keeping it queryable
- Charts: Build dashboards directly from MongoDB data without ETL
When to Use
Use this skill when:
- Designing or implementing mongodb architect solutions
- Reviewing or improving existing mongodb architect approaches
- Making architectural or implementation decisions about mongodb architect
- Learning mongodb architect patterns and best practices
- Troubleshooting mongodb architect-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Mongodb Architect Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement mongodb architect for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended mongodb architect approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When mongodb architect must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities