MongoDB Sharding Strategies for Enterprise Applications: Comprehensive Production Guide
Executive Summary
MongoDB sharding enables horizontal scaling for enterprise applications by distributing data across multiple servers. This comprehensive guide explores advanced sharding strategies, shard key design patterns, zone-based sharding for data locality, and migration approaches from replica sets to sharded clusters in production environments.
Key Architecture Benefits
Horizontal Scalability: Distribute data and load across multiple shards, enabling linear scaling for both storage capacity and query throughput.
Geographic Distribution: Zone sharding enables data locality compliance and reduced latency by placing data near users.
High Availability: Combined with replica sets, sharding provides both scalability and fault tolerance for mission-critical applications.
Workload Isolation: Separate read and write workloads across different shards, optimizing resource utilization and performance.
MongoDB Sharding Architecture
Sharded Cluster Components
# mongodb-architecture.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-sharding-architecture
namespace: mongodb
data:
architecture: |
┌─────────────────────────────────────────────────────────────────┐
│ MongoDB Sharded Cluster │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Application Layer │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ App 1 │ │ App 2 │ │ App 3 │ │ App N │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ └────────────┴────────────┴────────────┘ │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Query Routers (mongos) │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ mongos1 │ │ mongos2 │ │ mongos3 │ │ mongosN │ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ └────────────┴────────────┴────────────┘ │ │
│ └───────────────────────────┬─────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────┴─────────────────────────────┐ │
│ │ Config Servers │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Config 1 │ │ Config 2 │ │ Config 3 │ │ │
│ │ │ (Primary) │ │ (Secondary) │ │ (Secondary) │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Shard Clusters │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Shard 1 │ │ Shard 2 │ │ Shard N │ │ │
│ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │
│ │ │ │ Primary │ │ │ │ Primary │ │ │ │ Primary │ │ │ │
│ │ │ ├──────────┤ │ │ ├──────────┤ │ │ ├──────────┤ │ │ │
│ │ │ │Secondary1│ │ │ │Secondary1│ │ │ │Secondary1│ │ │ │
│ │ │ ├──────────┤ │ │ ├──────────┤ │ │ ├──────────┤ │ │ │
│ │ │ │Secondary2│ │ │ │Secondary2│ │ │ │Secondary2│ │ │ │
│ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Comprehensive Sharding Configuration
// sharding-config.js
// MongoDB Sharding Configuration Script
// Enable sharding for the database
sh.enableSharding("enterprise_db")
// Create indexes for shard keys
db.getSiblingDB("enterprise_db").users.createIndex(
{ "tenant_id": 1, "user_id": 1 },
{ background: true }
)
db.getSiblingDB("enterprise_db").transactions.createIndex(
{ "region": 1, "timestamp": 1 },
{ background: true }
)
db.getSiblingDB("enterprise_db").analytics.createIndex(
{ "date": 1, "customer_id": "hashed" },
{ background: true }
)
// Configure shard collections with different strategies
// 1. Range-based sharding for time-series data
sh.shardCollection(
"enterprise_db.transactions",
{ "region": 1, "timestamp": 1 },
false // unique
)
// 2. Hashed sharding for uniform distribution
sh.shardCollection(
"enterprise_db.users",
{ "user_id": "hashed" },
false
)
// 3. Compound shard key for multi-tenant applications
sh.shardCollection(
"enterprise_db.tenant_data",
{ "tenant_id": 1, "data_id": 1 },
true // unique
)
// 4. Zone-based sharding for geographic distribution
sh.shardCollection(
"enterprise_db.geo_data",
{ "country": 1, "city": 1, "_id": 1 },
false
)
// Configure zone sharding
sh.addShardToZone("shard01", "us-east")
sh.addShardToZone("shard02", "us-west")
sh.addShardToZone("shard03", "eu-west")
sh.addShardToZone("shard04", "ap-southeast")
// Define zone ranges
sh.updateZoneKeyRange(
"enterprise_db.geo_data",
{ "country": "US", "city": MinKey },
{ "country": "US", "city": MaxKey },
"us-east"
)
sh.updateZoneKeyRange(
"enterprise_db.geo_data",
{ "country": "UK", "city": MinKey },
{ "country": "UK", "city": MaxKey },
"eu-west"
)
sh.updateZoneKeyRange(
"enterprise_db.geo_data",
{ "country": "SG", "city": MinKey },
{ "country": "SG", "city": MaxKey },
"ap-southeast"
)
// Configure chunk size for optimal performance
use config
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: { value: 128 } }, // 128MB chunks
{ upsert: true }
)
// Configure balancer window
sh.setBalancerState(true)
db.settings.updateOne(
{ _id: "balancer" },
{
$set: {
activeWindow: {
start: "02:00",
stop: "06:00"
},
_secondaryThrottle: true,
attemptToBalanceJumboChunks: true
}
},
{ upsert: true }
)
// Configure autosplit
db.settings.updateOne(
{ _id: "autosplit" },
{ $set: { enabled: true } },
{ upsert: true }
)
Shard Key Selection Strategies
Advanced Shard Key Patterns
// shard-key-patterns.js
// Enterprise Shard Key Design Patterns
// Pattern 1: Time-series with bucketing
class TimeSeriesShardKeyDesign {
constructor() {
this.bucketSize = 3600; // 1 hour buckets
}
// Generate bucketed shard key for time-series data
generateShardKey(timestamp, deviceId) {
const bucket = Math.floor(timestamp / this.bucketSize) * this.bucketSize;
return {
bucket: bucket,
deviceId: deviceId,
timestamp: timestamp
};
}
// Create optimal indexes
createIndexes(db, collection) {
// Shard key index
db[collection].createIndex(
{ "bucket": 1, "deviceId": 1 },
{ background: true }
);
// Query optimization indexes
db[collection].createIndex(
{ "deviceId": 1, "timestamp": -1 },
{ background: true }
);
db[collection].createIndex(
{ "timestamp": -1 },
{
background: true,
expireAfterSeconds: 2592000 // 30 days TTL
}
);
}
// Configure sharding
enableSharding(sh, database, collection) {
sh.shardCollection(
`${database}.${collection}`,
{ "bucket": 1, "deviceId": 1 },
false
);
// Pre-split chunks for known time ranges
const now = new Date();
const startTime = now.getTime();
for (let i = 0; i < 24; i++) {
const splitPoint = {
bucket: Math.floor((startTime + (i * 3600000)) / this.bucketSize) * this.bucketSize,
deviceId: MinKey
};
sh.splitAt(`${database}.${collection}`, splitPoint);
}
}
}
// Pattern 2: Multi-tenant with tenant isolation
class MultiTenantShardKeyDesign {
// Generate compound shard key for tenant isolation
generateShardKey(tenantId, entityType, entityId) {
return {
tenantId: tenantId,
entityType: entityType,
entityId: entityId
};
}
// Configure tenant-based zones
configureTenantZones(sh, database, collection, tenantMappings) {
// Enable sharding with compound key
sh.shardCollection(
`${database}.${collection}`,
{ "tenantId": 1, "entityType": 1, "entityId": 1 },
false
);
// Assign large tenants to dedicated shards
for (const [tenantId, shardZone] of Object.entries(tenantMappings)) {
sh.updateZoneKeyRange(
`${database}.${collection}`,
{ "tenantId": tenantId, "entityType": MinKey, "entityId": MinKey },
{ "tenantId": tenantId, "entityType": MaxKey, "entityId": MaxKey },
shardZone
);
}
}
// Monitor tenant distribution
async analyzeTenantDistribution(db, collection) {
const pipeline = [
{
$group: {
_id: "$tenantId",
count: { $sum: 1 },
totalSize: { $sum: "$size" }
}
},
{
$sort: { totalSize: -1 }
},
{
$limit: 100
}
];
return await db[collection].aggregate(pipeline).toArray();
}
}
// Pattern 3: Geographic sharding with data locality
class GeographicShardKeyDesign {
constructor() {
this.regionMappings = {
'US': ['us-east-1', 'us-west-2'],
'EU': ['eu-west-1', 'eu-central-1'],
'APAC': ['ap-southeast-1', 'ap-northeast-1']
};
}
// Generate location-aware shard key
generateShardKey(location, customerId, timestamp) {
const region = this.getRegionFromLocation(location);
return {
region: region,
customerId: customerId,
timestamp: timestamp
};
}
// Get region from location coordinates or country
getRegionFromLocation(location) {
if (location.country) {
// Country-based mapping
const countryRegionMap = {
'US': 'US', 'CA': 'US', 'MX': 'US',
'UK': 'EU', 'DE': 'EU', 'FR': 'EU',
'JP': 'APAC', 'SG': 'APAC', 'AU': 'APAC'
};
return countryRegionMap[location.country] || 'GLOBAL';
} else if (location.coordinates) {
// Coordinate-based mapping
const [lng, lat] = location.coordinates;
if (lng >= -180 && lng <= -30) return 'US';
if (lng >= -30 && lng <= 60) return 'EU';
if (lng >= 60 && lng <= 180) return 'APAC';
}
return 'GLOBAL';
}
// Configure geographic zones
configureGeographicZones(sh, database, collection) {
sh.shardCollection(
`${database}.${collection}`,
{ "region": 1, "customerId": 1, "timestamp": 1 },
false
);
// Configure zones for each region
for (const [region, shards] of Object.entries(this.regionMappings)) {
shards.forEach(shard => {
sh.addShardToZone(shard, region);
});
sh.updateZoneKeyRange(
`${database}.${collection}`,
{ "region": region, "customerId": MinKey, "timestamp": MinKey },
{ "region": region, "customerId": MaxKey, "timestamp": MaxKey },
region
);
}
}
}
// Pattern 4: Hybrid shard key for complex workloads
class HybridShardKeyDesign {
// Combine multiple strategies for optimal distribution
generateShardKey(tenantId, category, timestamp, entityId) {
// Use hashed component for distribution
const hashedComponent = this.hashString(tenantId + category);
// Use range component for queries
const timeComponent = Math.floor(timestamp / 86400000); // Daily buckets
return {
hashBucket: hashedComponent % 1000, // 1000 buckets
timeComponent: timeComponent,
tenantId: tenantId,
entityId: entityId
};
}
hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash);
}
// Pre-split for optimal initial distribution
preSplitCollection(sh, database, collection, numInitialChunks = 100) {
sh.shardCollection(
`${database}.${collection}`,
{ "hashBucket": 1, "timeComponent": 1, "tenantId": 1, "entityId": 1 },
false
);
// Pre-split based on hash buckets
for (let i = 1; i < numInitialChunks; i++) {
const splitPoint = {
hashBucket: Math.floor((1000 / numInitialChunks) * i),
timeComponent: MinKey,
tenantId: MinKey,
entityId: MinKey
};
sh.splitAt(`${database}.${collection}`, splitPoint);
}
}
}
Shard Key Analysis Tools
// shard-key-analyzer.js
// Tools for analyzing shard key effectiveness
class ShardKeyAnalyzer {
constructor(db, collection) {
this.db = db;
this.collection = collection;
}
// Analyze shard key cardinality
async analyzeCardinality(shardKey) {
const pipeline = [
{
$group: {
_id: shardKey,
count: { $sum: 1 }
}
},
{
$group: {
_id: null,
uniqueValues: { $sum: 1 },
totalDocuments: { $sum: "$count" },
distribution: {
$push: {
key: "$_id",
count: "$count"
}
}
}
}
];
const result = await this.db[this.collection]
.aggregate(pipeline, { allowDiskUse: true })
.toArray();
if (result.length > 0) {
const stats = result[0];
const cardinality = stats.uniqueValues / stats.totalDocuments;
// Calculate distribution metrics
const counts = stats.distribution.map(d => d.count);
const mean = counts.reduce((a, b) => a + b, 0) / counts.length;
const variance = counts.reduce((sum, count) =>
sum + Math.pow(count - mean, 2), 0) / counts.length;
const stdDev = Math.sqrt(variance);
const cv = stdDev / mean; // Coefficient of variation
return {
cardinality: cardinality,
uniqueValues: stats.uniqueValues,
totalDocuments: stats.totalDocuments,
distributionScore: 1 - cv, // Higher is better
recommendation: this.getCardinalityRecommendation(cardinality, cv)
};
}
}
getCardinalityRecommendation(cardinality, cv) {
if (cardinality < 0.01) {
return "Low cardinality - consider adding more fields to shard key";
} else if (cardinality > 0.95) {
return "Very high cardinality - good for even distribution";
} else if (cv > 0.5) {
return "Uneven distribution - some shard keys have significantly more documents";
} else {
return "Good cardinality and distribution";
}
}
// Analyze query patterns
async analyzeQueryPatterns(duration = 3600000) { // 1 hour
const profileCollection = this.db.getSiblingDB("system").profile;
const endTime = new Date();
const startTime = new Date(endTime - duration);
const pipeline = [
{
$match: {
ns: `${this.db.getName()}.${this.collection}`,
ts: { $gte: startTime, $lte: endTime },
op: { $in: ["query", "find", "aggregate", "update", "remove"] }
}
},
{
$project: {
op: 1,
command: 1,
filter: { $ifNull: ["$command.filter", "$command.query", {}] },
millis: 1,
keysExamined: 1,
docsExamined: 1,
nreturned: 1
}
},
{
$group: {
_id: {
op: "$op",
filterKeys: { $objectToArray: "$filter" }
},
count: { $sum: 1 },
avgMillis: { $avg: "$millis" },
totalMillis: { $sum: "$millis" },
avgKeysExamined: { $avg: "$keysExamined" },
avgDocsExamined: { $avg: "$docsExamined" }
}
},
{
$sort: { totalMillis: -1 }
},
{
$limit: 20
}
];
return await profileCollection.aggregate(pipeline).toArray();
}
// Analyze chunk distribution
async analyzeChunkDistribution() {
const configDB = this.db.getSiblingDB("config");
const namespace = `${this.db.getName()}.${this.collection}`;
// Get chunk distribution by shard
const chunkPipeline = [
{
$match: { ns: namespace }
},
{
$group: {
_id: "$shard",
count: { $sum: 1 },
minBound: { $min: "$min" },
maxBound: { $max: "$max" }
}
},
{
$sort: { count: -1 }
}
];
const chunkDistribution = await configDB.chunks
.aggregate(chunkPipeline)
.toArray();
// Calculate jumbo chunks
const jumboChunks = await configDB.chunks.countDocuments({
ns: namespace,
jumbo: true
});
// Get collection stats from each shard
const collStats = await this.db.runCommand({
collStats: this.collection,
indexDetails: false
});
return {
chunkDistribution: chunkDistribution,
totalChunks: chunkDistribution.reduce((sum, shard) => sum + shard.count, 0),
jumboChunks: jumboChunks,
shardStats: collStats.shards,
recommendation: this.getDistributionRecommendation(chunkDistribution, jumboChunks)
};
}
getDistributionRecommendation(distribution, jumboChunks) {
const counts = distribution.map(s => s.count);
const max = Math.max(...counts);
const min = Math.min(...counts);
const imbalanceRatio = max / min;
const recommendations = [];
if (imbalanceRatio > 2) {
recommendations.push("Significant chunk imbalance detected - consider rebalancing");
}
if (jumboChunks > 0) {
recommendations.push(`${jumboChunks} jumbo chunks detected - may need to refine shard key`);
}
return recommendations.length > 0 ?
recommendations.join("; ") :
"Chunk distribution is balanced";
}
}
Zone Sharding Implementation
Geographic Data Distribution
// zone-sharding-config.js
// Advanced Zone Sharding Configuration
class ZoneShardingManager {
constructor(sh) {
this.sh = sh;
this.zones = new Map();
}
// Define geographic zones with compliance requirements
defineGeographicZones() {
const zones = [
{
name: "eu-gdpr",
shards: ["shard-eu-1", "shard-eu-2"],
countries: ["DE", "FR", "UK", "IT", "ES", "NL", "BE", "PL"],
compliance: ["GDPR"],
replication: { w: "majority", j: true }
},
{
name: "us-east",
shards: ["shard-us-east-1", "shard-us-east-2"],
states: ["NY", "NJ", "PA", "MA", "CT", "VT", "NH", "ME"],
compliance: ["SOC2", "HIPAA"],
replication: { w: 2, j: true }
},
{
name: "us-west",
shards: ["shard-us-west-1", "shard-us-west-2"],
states: ["CA", "OR", "WA", "NV", "AZ"],
compliance: ["CCPA", "SOC2"],
replication: { w: 2, j: true }
},
{
name: "apac",
shards: ["shard-apac-1", "shard-apac-2", "shard-apac-3"],
countries: ["JP", "SG", "AU", "NZ", "KR", "IN"],
compliance: ["PDPA"],
replication: { w: "majority", j: true }
},
{
name: "china",
shards: ["shard-cn-1", "shard-cn-2"],
countries: ["CN"],
compliance: ["PIPL"],
replication: { w: 3, j: true },
isolated: true // Data sovereignty requirement
}
];
zones.forEach(zone => {
this.zones.set(zone.name, zone);
this.configureZone(zone);
});
}
// Configure zone with shards and tags
configureZone(zone) {
// Add shards to zone
zone.shards.forEach(shard => {
this.sh.addShardToZone(shard, zone.name);
});
// Configure zone-specific settings
if (zone.isolated) {
// Ensure data doesn't leave the zone
this.configureIsolatedZone(zone);
}
}
// Configure isolated zone for data sovereignty
configureIsolatedZone(zone) {
// Set zone-specific balancer settings
const config = this.sh._getConfigDB();
config.settings.updateOne(
{ _id: `balancer-${zone.name}` },
{
$set: {
zone: zone.name,
isolated: true,
crossZoneBalancing: false
}
},
{ upsert: true }
);
}
// Configure collection for zone sharding
enableZoneSharding(database, collection, zoneKeyField, additionalKeys = []) {
const namespace = `${database}.${collection}`;
// Build shard key
const shardKey = { [zoneKeyField]: 1 };
additionalKeys.forEach(key => {
shardKey[key] = 1;
});
// Enable sharding
this.sh.shardCollection(namespace, shardKey);
// Configure zone ranges for each zone
this.zones.forEach((zone, zoneName) => {
if (zone.countries) {
zone.countries.forEach(country => {
this.sh.updateZoneKeyRange(
namespace,
{ [zoneKeyField]: country, ...this.getMinKeys(additionalKeys) },
{ [zoneKeyField]: country, ...this.getMaxKeys(additionalKeys) },
zoneName
);
});
}
if (zone.states) {
zone.states.forEach(state => {
this.sh.updateZoneKeyRange(
namespace,
{ [zoneKeyField]: state, ...this.getMinKeys(additionalKeys) },
{ [zoneKeyField]: state, ...this.getMaxKeys(additionalKeys) },
zoneName
);
});
}
});
}
getMinKeys(keys) {
const minKeys = {};
keys.forEach(key => {
minKeys[key] = MinKey;
});
return minKeys;
}
getMaxKeys(keys) {
const maxKeys = {};
keys.forEach(key => {
maxKeys[key] = MaxKey;
});
return maxKeys;
}
// Move chunks between zones
async moveChunkToZone(namespace, chunk, targetZone) {
const zone = this.zones.get(targetZone);
if (!zone) {
throw new Error(`Zone ${targetZone} not found`);
}
// Select target shard in the zone
const targetShard = zone.shards[Math.floor(Math.random() * zone.shards.length)];
// Move chunk
try {
const result = await this.sh._adminCommand({
moveChunk: namespace,
bounds: [chunk.min, chunk.max],
to: targetShard,
_secondaryThrottle: true,
writeConcern: zone.replication
});
return {
success: true,
chunk: chunk,
targetShard: targetShard,
result: result
};
} catch (error) {
return {
success: false,
chunk: chunk,
error: error.message
};
}
}
// Rebalance zones based on data distribution
async rebalanceZones(namespace) {
const configDB = this.sh._getConfigDB();
// Analyze current distribution
const distribution = await configDB.chunks.aggregate([
{ $match: { ns: namespace } },
{
$lookup: {
from: "shards",
localField: "shard",
foreignField: "_id",
as: "shardInfo"
}
},
{
$group: {
_id: "$shard",
count: { $sum: 1 },
dataSize: { $sum: "$dataSize" }
}
}
]).toArray();
// Calculate target distribution
const totalChunks = distribution.reduce((sum, shard) => sum + shard.count, 0);
const avgChunksPerShard = totalChunks / distribution.length;
// Identify over and under-utilized shards
const rebalanceOperations = [];
distribution.forEach(shard => {
const deviation = shard.count - avgChunksPerShard;
if (Math.abs(deviation) > avgChunksPerShard * 0.1) { // 10% threshold
rebalanceOperations.push({
shard: shard._id,
currentChunks: shard.count,
targetChunks: Math.round(avgChunksPerShard),
chunksToMove: Math.round(deviation)
});
}
});
return {
distribution: distribution,
avgChunksPerShard: avgChunksPerShard,
rebalanceNeeded: rebalanceOperations.length > 0,
operations: rebalanceOperations
};
}
}
// Zone-aware application code
class ZoneAwareMongoClient {
constructor(connectionString, options = {}) {
this.client = new MongoClient(connectionString, {
...options,
readPreference: "nearest",
readConcern: { level: "majority" },
writeConcern: { w: "majority", j: true }
});
this.zoneMapping = new Map();
this.initializeZoneMapping();
}
initializeZoneMapping() {
// Map regions to read preferences
this.zoneMapping.set("eu-gdpr", {
readPreference: "nearest",
readPreferenceTags: [{ zone: "eu-gdpr" }]
});
this.zoneMapping.set("us-east", {
readPreference: "nearest",
readPreferenceTags: [{ zone: "us-east" }]
});
this.zoneMapping.set("china", {
readPreference: "primary", // Ensure data doesn't leave zone
readPreferenceTags: [{ zone: "china" }]
});
}
// Get collection with zone-aware read preference
getCollection(database, collection, userRegion) {
const zoneConfig = this.zoneMapping.get(userRegion) || {
readPreference: "nearest"
};
return this.client
.db(database)
.collection(collection)
.withOptions(zoneConfig);
}
// Execute zone-aware query
async findWithZoneAwareness(database, collection, query, userLocation) {
const zone = this.getZoneFromLocation(userLocation);
const coll = this.getCollection(database, collection, zone);
// Add zone hint to query for optimization
const zoneAwareQuery = {
...query,
_zoneHint: zone
};
return await coll.find(zoneAwareQuery).toArray();
}
getZoneFromLocation(location) {
// Implement logic to map location to zone
if (location.country === "CN") return "china";
if (["DE", "FR", "UK"].includes(location.country)) return "eu-gdpr";
if (location.country === "US") {
if (["NY", "NJ", "PA"].includes(location.state)) return "us-east";
if (["CA", "OR", "WA"].includes(location.state)) return "us-west";
}
if (["JP", "SG", "AU"].includes(location.country)) return "apac";
return "default";
}
}
Performance Optimization
Query Optimization for Sharded Clusters
// sharding-performance-optimizer.js
// Advanced performance optimization for sharded MongoDB
class ShardingPerformanceOptimizer {
constructor(db, collection) {
this.db = db;
this.collection = collection;
}
// Optimize queries for sharded environment
async optimizeQuery(query, options = {}) {
const queryPlan = await this.analyzeQueryPlan(query);
const optimizedQuery = this.applyOptimizations(query, queryPlan);
return {
originalQuery: query,
optimizedQuery: optimizedQuery,
expectedImprovement: queryPlan.improvement,
recommendations: queryPlan.recommendations
};
}
// Analyze query execution plan
async analyzeQueryPlan(query) {
const explainResult = await this.db[this.collection]
.find(query)
.explain("executionStats");
const analysis = {
usesShardKey: false,
targetedShards: [],
scatterGather: false,
indexUsage: [],
recommendations: []
};
// Check if query targets specific shards
if (explainResult.queryPlanner) {
const winningPlan = explainResult.queryPlanner.winningPlan;
if (winningPlan.shards) {
analysis.targetedShards = winningPlan.shards.map(s => s.shardName);
analysis.scatterGather = winningPlan.shards.length > 1;
}
// Check index usage
if (winningPlan.inputStage && winningPlan.inputStage.indexName) {
analysis.indexUsage.push(winningPlan.inputStage.indexName);
analysis.usesShardKey = winningPlan.inputStage.indexName.includes("shard");
}
}
// Generate recommendations
if (analysis.scatterGather) {
analysis.recommendations.push(
"Query causes scatter-gather. Include shard key in query filter."
);
}
if (!analysis.usesShardKey) {
analysis.recommendations.push(
"Query doesn't use shard key. Consider adding shard key fields."
);
}
if (analysis.indexUsage.length === 0) {
analysis.recommendations.push(
"No index used. Create appropriate indexes for query patterns."
);
}
return analysis;
}
// Apply query optimizations
applyOptimizations(query, queryPlan) {
let optimizedQuery = { ...query };
// Add shard key hints if missing
if (!queryPlan.usesShardKey && this.shardKey) {
optimizedQuery = this.addShardKeyHint(optimizedQuery);
}
// Optimize sort operations
if (query.sort) {
optimizedQuery.sort = this.optimizeSortOrder(query.sort);
}
// Add read preference for read-heavy queries
if (!query.writeConcern) {
optimizedQuery.readPreference = "secondaryPreferred";
}
return optimizedQuery;
}
// Batch operations for better performance
createBatchProcessor(batchSize = 1000) {
return new BatchProcessor(this.db, this.collection, batchSize);
}
// Parallel processing across shards
async parallelProcess(operation, options = {}) {
const shards = await this.getShardList();
const promises = [];
for (const shard of shards) {
const shardOperation = this.createShardSpecificOperation(
operation,
shard,
options
);
promises.push(shardOperation);
}
const results = await Promise.allSettled(promises);
return this.aggregateShardResults(results);
}
// Create shard-specific operation
createShardSpecificOperation(operation, shard, options) {
return new Promise(async (resolve, reject) => {
try {
// Add shard targeting hint
const shardTargetedOp = {
...operation,
$readPreference: {
mode: "primary",
tags: [{ shard: shard.name }]
}
};
const result = await this.executeOperation(shardTargetedOp);
resolve({
shard: shard.name,
result: result,
metrics: {
executionTime: result.executionTimeMillis,
documentsExamined: result.docsExamined
}
});
} catch (error) {
reject({
shard: shard.name,
error: error.message
});
}
});
}
// Index optimization for sharded collections
async optimizeIndexes() {
const currentIndexes = await this.db[this.collection].getIndexes();
const queryPatterns = await this.analyzeQueryPatterns();
const recommendations = [];
// Check for missing shard key index
const shardKeyIndex = currentIndexes.find(idx =>
Object.keys(idx.key).includes(this.shardKey)
);
if (!shardKeyIndex) {
recommendations.push({
action: "create",
index: { [this.shardKey]: 1 },
reason: "Missing shard key index"
});
}
// Analyze query patterns for compound indexes
for (const pattern of queryPatterns) {
const optimalIndex = this.suggestOptimalIndex(pattern, currentIndexes);
if (optimalIndex) {
recommendations.push(optimalIndex);
}
}
// Check for redundant indexes
const redundantIndexes = this.findRedundantIndexes(currentIndexes);
for (const index of redundantIndexes) {
recommendations.push({
action: "drop",
index: index.name,
reason: "Redundant with other indexes"
});
}
return recommendations;
}
}
// Batch processor for efficient bulk operations
class BatchProcessor {
constructor(db, collection, batchSize = 1000) {
this.db = db;
this.collection = collection;
this.batchSize = batchSize;
this.queue = [];
this.processing = false;
}
// Add operation to batch
add(operation) {
this.queue.push(operation);
if (this.queue.length >= this.batchSize) {
this.flush();
}
}
// Process batch
async flush() {
if (this.queue.length === 0 || this.processing) {
return;
}
this.processing = true;
const batch = this.queue.splice(0, this.batchSize);
try {
// Group operations by type
const grouped = this.groupOperations(batch);
// Execute each group in parallel
const promises = [];
if (grouped.inserts.length > 0) {
promises.push(this.bulkInsert(grouped.inserts));
}
if (grouped.updates.length > 0) {
promises.push(this.bulkUpdate(grouped.updates));
}
if (grouped.deletes.length > 0) {
promises.push(this.bulkDelete(grouped.deletes));
}
const results = await Promise.all(promises);
return this.aggregateResults(results);
} finally {
this.processing = false;
// Process remaining items
if (this.queue.length > 0) {
await this.flush();
}
}
}
// Group operations by type
groupOperations(operations) {
const grouped = {
inserts: [],
updates: [],
deletes: []
};
for (const op of operations) {
switch (op.type) {
case 'insert':
grouped.inserts.push(op.document);
break;
case 'update':
grouped.updates.push({
filter: op.filter,
update: op.update,
options: op.options
});
break;
case 'delete':
grouped.deletes.push(op.filter);
break;
}
}
return grouped;
}
// Bulk insert with ordered execution
async bulkInsert(documents) {
const bulkOp = this.db[this.collection].initializeOrderedBulkOp();
for (const doc of documents) {
// Add shard key if missing
if (!doc[this.shardKey]) {
doc[this.shardKey] = this.generateShardKey(doc);
}
bulkOp.insert(doc);
}
return await bulkOp.execute();
}
// Bulk update with sharding awareness
async bulkUpdate(updates) {
const bulkOp = this.db[this.collection].initializeUnorderedBulkOp();
for (const update of updates) {
// Ensure shard key is in filter for targeted updates
if (!update.filter[this.shardKey]) {
console.warn("Update missing shard key in filter - may cause scatter-gather");
}
bulkOp.find(update.filter).update(update.update);
}
return await bulkOp.execute();
}
}
Connection Pooling and Load Balancing
// sharded-connection-manager.js
// Advanced connection management for sharded clusters
class ShardedConnectionManager {
constructor(config) {
this.config = config;
this.mongosConnections = new Map();
this.shardConnections = new Map();
this.healthChecker = new HealthChecker(this);
this.initializeConnections();
}
// Initialize connections to all mongos instances
async initializeConnections() {
for (const mongos of this.config.mongosServers) {
const connection = await this.createMongosConnection(mongos);
this.mongosConnections.set(mongos.name, connection);
}
// Start health monitoring
this.healthChecker.start();
}
// Create optimized mongos connection
async createMongosConnection(mongosConfig) {
const options = {
// Connection pool settings
maxPoolSize: mongosConfig.maxPoolSize || 100,
minPoolSize: mongosConfig.minPoolSize || 10,
maxIdleTimeMS: 60000,
waitQueueTimeoutMS: 30000,
// Socket settings
socketTimeoutMS: 300000,
connectTimeoutMS: 30000,
// Server selection
serverSelectionTimeoutMS: 30000,
localThresholdMS: 15,
// Read/Write preferences
readPreference: mongosConfig.readPreference || "primaryPreferred",
readConcern: { level: "majority" },
writeConcern: {
w: "majority",
j: true,
wtimeout: 30000
},
// Retry settings
retryWrites: true,
retryReads: true,
// Monitoring
monitorCommands: true,
// Load balancing
loadBalanced: true
};
const client = new MongoClient(mongosConfig.url, options);
await client.connect();
// Set up connection monitoring
this.setupConnectionMonitoring(client, mongosConfig.name);
return {
client: client,
config: mongosConfig,
metrics: {
activeConnections: 0,
totalRequests: 0,
errorCount: 0,
avgResponseTime: 0
}
};
}
// Setup connection monitoring
setupConnectionMonitoring(client, name) {
// Monitor command starts
client.on('commandStarted', (event) => {
this.updateMetrics(name, 'commandStarted', event);
});
// Monitor command completions
client.on('commandSucceeded', (event) => {
this.updateMetrics(name, 'commandSucceeded', event);
});
// Monitor command failures
client.on('commandFailed', (event) => {
this.updateMetrics(name, 'commandFailed', event);
});
// Monitor connection pool events
client.on('connectionPoolCreated', (event) => {
console.log(`Connection pool created for ${name}`);
});
client.on('connectionPoolClosed', (event) => {
console.log(`Connection pool closed for ${name}`);
});
}
// Get optimal connection based on load
getOptimalConnection(operation = 'read') {
let optimalConnection = null;
let minLoad = Infinity;
for (const [name, connection] of this.mongosConnections) {
if (!connection.healthy) continue;
const load = this.calculateConnectionLoad(connection);
// Apply operation-specific preferences
if (operation === 'write' && connection.config.preferredForWrites) {
load *= 0.8; // Give preference to write-optimized mongos
} else if (operation === 'read' && connection.config.preferredForReads) {
load *= 0.8; // Give preference to read-optimized mongos
}
if (load < minLoad) {
minLoad = load;
optimalConnection = connection;
}
}
return optimalConnection;
}
// Calculate connection load
calculateConnectionLoad(connection) {
const metrics = connection.metrics;
// Composite load score based on multiple factors
const activeConnectionsScore = metrics.activeConnections / connection.config.maxPoolSize;
const errorRate = metrics.errorCount / Math.max(metrics.totalRequests, 1);
const responseTimeScore = metrics.avgResponseTime / 1000; // Normalize to seconds
// Weighted load calculation
const load = (activeConnectionsScore * 0.4) +
(errorRate * 0.3) +
(responseTimeScore * 0.3);
return load;
}
// Execute operation with automatic failover
async executeWithFailover(operation, options = {}) {
const maxRetries = options.maxRetries || 3;
let lastError = null;
for (let i = 0; i < maxRetries; i++) {
const connection = this.getOptimalConnection(options.type);
if (!connection) {
throw new Error("No healthy mongos connections available");
}
try {
const startTime = Date.now();
const result = await operation(connection.client);
// Update success metrics
this.updateResponseTime(connection, Date.now() - startTime);
connection.metrics.totalRequests++;
return result;
} catch (error) {
lastError = error;
connection.metrics.errorCount++;
// Check if error is retryable
if (!this.isRetryableError(error)) {
throw error;
}
// Mark connection as unhealthy if too many errors
if (connection.metrics.errorCount > 10) {
connection.healthy = false;
}
// Exponential backoff
await this.sleep(Math.pow(2, i) * 100);
}
}
throw lastError;
}
// Check if error is retryable
isRetryableError(error) {
const retryableErrors = [
'NetworkTimeout',
'ConnectionClosed',
'NotPrimaryError',
'NodeNotFound'
];
return retryableErrors.some(errType =>
error.message.includes(errType) || error.code === errType
);
}
// Update response time metrics
updateResponseTime(connection, responseTime) {
const metrics = connection.metrics;
const alpha = 0.2; // Exponential moving average factor
if (metrics.avgResponseTime === 0) {
metrics.avgResponseTime = responseTime;
} else {
metrics.avgResponseTime =
alpha * responseTime + (1 - alpha) * metrics.avgResponseTime;
}
}
// Close all connections
async close() {
this.healthChecker.stop();
const promises = [];
for (const [name, connection] of this.mongosConnections) {
promises.push(connection.client.close());
}
await Promise.all(promises);
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Health checker for connections
class HealthChecker {
constructor(connectionManager) {
this.connectionManager = connectionManager;
this.interval = null;
}
start() {
this.interval = setInterval(() => {
this.checkHealth();
}, 30000); // Check every 30 seconds
}
stop() {
if (this.interval) {
clearInterval(this.interval);
}
}
async checkHealth() {
for (const [name, connection] of this.connectionManager.mongosConnections) {
try {
// Ping the server
await connection.client.db('admin').command({ ping: 1 });
// Check shard status
const shardStatus = await connection.client.db('admin').command({
listShards: 1
});
connection.healthy = true;
connection.shardInfo = shardStatus.shards;
// Reset error count on successful health check
if (connection.metrics.errorCount > 0) {
connection.metrics.errorCount = Math.floor(
connection.metrics.errorCount * 0.5
);
}
} catch (error) {
console.error(`Health check failed for ${name}:`, error);
connection.healthy = false;
}
}
}
}
Migration from Replica Sets to Sharded Clusters
Migration Strategy and Implementation
// replica-to-shard-migration.js
// Zero-downtime migration from replica set to sharded cluster
class ReplicaSetToShardMigration {
constructor(sourceReplSet, targetCluster) {
this.sourceReplSet = sourceReplSet;
this.targetCluster = targetCluster;
this.migrationState = new MigrationState();
this.validator = new MigrationValidator();
}
// Execute complete migration
async executeMigration(options = {}) {
const startTime = new Date();
try {
// Phase 1: Pre-migration validation
console.log("Phase 1: Pre-migration validation");
await this.preMigrationValidation();
// Phase 2: Initial data sync
console.log("Phase 2: Initial data sync");
await this.initialDataSync(options);
// Phase 3: Setup change stream sync
console.log("Phase 3: Setting up change stream sync");
const changeStreamSync = await this.setupChangeStreamSync();
// Phase 4: Final sync and validation
console.log("Phase 4: Final sync and validation");
await this.finalSync();
// Phase 5: Application cutover
console.log("Phase 5: Application cutover");
await this.performCutover(options);
// Phase 6: Post-migration validation
console.log("Phase 6: Post-migration validation");
await this.postMigrationValidation();
const endTime = new Date();
const duration = (endTime - startTime) / 1000;
return {
success: true,
duration: duration,
statistics: this.migrationState.getStatistics()
};
} catch (error) {
await this.rollback();
throw error;
}
}
// Pre-migration validation
async preMigrationValidation() {
const validations = [];
// Check source replica set health
validations.push(this.validator.validateReplicaSetHealth(this.sourceReplSet));
// Check target cluster health
validations.push(this.validator.validateShardedClusterHealth(this.targetCluster));
// Validate shard key choices
validations.push(this.validator.validateShardKeys(this.migrationState.shardKeyMappings));
// Check disk space
validations.push(this.validator.validateDiskSpace(
this.sourceReplSet,
this.targetCluster
));
const results = await Promise.all(validations);
const failures = results.filter(r => !r.passed);
if (failures.length > 0) {
throw new Error(`Pre-migration validation failed: ${JSON.stringify(failures)}`);
}
}
// Initial data sync
async initialDataSync(options) {
const collections = await this.getCollectionsToMigrate();
for (const collection of collections) {
await this.syncCollection(collection, options);
}
}
// Sync individual collection
async syncCollection(collection, options) {
const { database, name } = collection;
const namespace = `${database}.${name}`;
console.log(`Syncing collection: ${namespace}`);
// Get collection metadata
const sourceDB = this.sourceReplSet.db(database);
const targetDB = this.targetCluster.db(database);
const collStats = await sourceDB.command({
collStats: name,
indexDetails: false
});
const docCount = collStats.count;
const dataSize = collStats.size;
console.log(`Collection ${namespace}: ${docCount} documents, ${dataSize} bytes`);
// Create indexes first
await this.syncIndexes(sourceDB, targetDB, name);
// Enable sharding for the collection
await this.enableShardingForCollection(database, name, collection.shardKey);
// Sync data in batches
const batchSize = options.batchSize || 10000;
let processed = 0;
const cursor = sourceDB.collection(name).find({}).batchSize(batchSize);
while (await cursor.hasNext()) {
const batch = [];
for (let i = 0; i < batchSize && await cursor.hasNext(); i++) {
const doc = await cursor.next();
batch.push(doc);
}
if (batch.length > 0) {
await this.insertBatch(targetDB.collection(name), batch);
processed += batch.length;
this.migrationState.updateProgress(namespace, processed, docCount);
// Throttle if needed
if (options.throttleMs) {
await this.sleep(options.throttleMs);
}
}
}
await cursor.close();
}
// Sync indexes
async syncIndexes(sourceDB, targetDB, collectionName) {
const sourceIndexes = await sourceDB.collection(collectionName).getIndexes();
for (const index of sourceIndexes) {
if (index.name === '_id_') continue; // Skip default _id index
const indexSpec = { ...index };
delete indexSpec.v;
delete indexSpec.ns;
try {
await targetDB.collection(collectionName).createIndex(
index.key,
{
name: index.name,
...indexSpec
}
);
console.log(`Created index ${index.name} on ${collectionName}`);
} catch (error) {
if (error.code !== 86) { // Index already exists
throw error;
}
}
}
}
// Setup change stream synchronization
async setupChangeStreamSync() {
const changeStreams = new Map();
const collections = await this.getCollectionsToMigrate();
for (const collection of collections) {
const { database, name } = collection;
const namespace = `${database}.${name}`;
const changeStream = this.sourceReplSet
.db(database)
.collection(name)
.watch([], {
fullDocument: 'updateLookup',
resumeAfter: this.migrationState.getResumeToken(namespace)
});
changeStream.on('change', async (change) => {
await this.processChangeEvent(change, database, name);
});
changeStream.on('error', (error) => {
console.error(`Change stream error for ${namespace}:`, error);
this.handleChangeStreamError(namespace, error);
});
changeStreams.set(namespace, changeStream);
}
this.migrationState.changeStreams = changeStreams;
return changeStreams;
}
// Process change stream event
async processChangeEvent(change, database, collectionName) {
const targetCollection = this.targetCluster
.db(database)
.collection(collectionName);
try {
switch (change.operationType) {
case 'insert':
await targetCollection.insertOne(change.fullDocument);
break;
case 'update':
await targetCollection.replaceOne(
{ _id: change.documentKey._id },
change.fullDocument,
{ upsert: true }
);
break;
case 'replace':
await targetCollection.replaceOne(
{ _id: change.documentKey._id },
change.fullDocument,
{ upsert: true }
);
break;
case 'delete':
await targetCollection.deleteOne({ _id: change.documentKey._id });
break;
}
// Store resume token
this.migrationState.updateResumeToken(
`${database}.${collectionName}`,
change._id
);
} catch (error) {
console.error(`Error processing change event:`, error);
throw error;
}
}
// Final sync phase
async finalSync() {
// Pause application writes if possible
console.log("Performing final sync...");
// Wait for change streams to catch up
await this.waitForChangeStreamsCatchUp();
// Verify data consistency
const consistent = await this.validator.validateDataConsistency(
this.sourceReplSet,
this.targetCluster
);
if (!consistent) {
throw new Error("Data consistency validation failed");
}
}
// Perform application cutover
async performCutover(options) {
if (options.dryRun) {
console.log("Dry run mode - skipping actual cutover");
return;
}
// Update application configuration
console.log("Updating application configuration...");
// This would typically involve:
// 1. Updating connection strings
// 2. Deploying new application version
// 3. Gradual traffic shift
// For this example, we'll simulate the cutover
await this.updateApplicationConfig({
connectionString: this.targetCluster.connectionString,
readPreference: "primaryPreferred",
writeConcern: { w: "majority", j: true }
});
}
// Post-migration validation
async postMigrationValidation() {
const validations = [];
// Verify document counts
validations.push(this.validator.validateDocumentCounts(
this.sourceReplSet,
this.targetCluster
));
// Verify shard distribution
validations.push(this.validator.validateShardDistribution(
this.targetCluster
));
// Test application connectivity
validations.push(this.validator.validateApplicationConnectivity(
this.targetCluster
));
const results = await Promise.all(validations);
const failures = results.filter(r => !r.passed);
if (failures.length > 0) {
console.warn("Post-migration validation warnings:", failures);
}
}
// Rollback mechanism
async rollback() {
console.log("Initiating rollback...");
try {
// Stop change streams
if (this.migrationState.changeStreams) {
for (const [namespace, stream] of this.migrationState.changeStreams) {
await stream.close();
}
}
// Revert application configuration
await this.revertApplicationConfig();
// Clean up target cluster if needed
if (this.migrationState.phase === 'initial_sync') {
await this.cleanupTargetCluster();
}
console.log("Rollback completed");
} catch (error) {
console.error("Error during rollback:", error);
throw error;
}
}
}
// Migration state management
class MigrationState {
constructor() {
this.phase = 'not_started';
this.progress = new Map();
this.resumeTokens = new Map();
this.startTime = null;
this.statistics = {
totalDocuments: 0,
migratedDocuments: 0,
errors: 0
};
}
updateProgress(namespace, processed, total) {
this.progress.set(namespace, {
processed: processed,
total: total,
percentage: (processed / total) * 100
});
// Update overall statistics
this.statistics.migratedDocuments =
Array.from(this.progress.values())
.reduce((sum, p) => sum + p.processed, 0);
}
updateResumeToken(namespace, token) {
this.resumeTokens.set(namespace, token);
}
getResumeToken(namespace) {
return this.resumeTokens.get(namespace);
}
getStatistics() {
return {
...this.statistics,
progress: Object.fromEntries(this.progress),
duration: this.startTime ?
(new Date() - this.startTime) / 1000 : 0
};
}
}
// Migration validator
class MigrationValidator {
async validateReplicaSetHealth(replSet) {
try {
const status = await replSet.admin().command({ replSetGetStatus: 1 });
// Check if all members are healthy
const unhealthyMembers = status.members.filter(m =>
m.state !== 1 && m.state !== 2 // PRIMARY or SECONDARY
);
return {
passed: unhealthyMembers.length === 0,
message: unhealthyMembers.length === 0 ?
"Replica set is healthy" :
`Unhealthy members: ${unhealthyMembers.map(m => m.name).join(', ')}`
};
} catch (error) {
return {
passed: false,
message: `Failed to check replica set health: ${error.message}`
};
}
}
async validateShardedClusterHealth(cluster) {
try {
// Check shard status
const shardStatus = await cluster.admin().command({ listShards: 1 });
// Check config server status
const configStatus = await cluster.admin().command({
replSetGetStatus: 1,
$readPreference: { mode: "primaryPreferred" }
});
// Check balancer status
const balancerStatus = await cluster.admin().command({
balancerStatus: 1
});
return {
passed: true,
message: "Sharded cluster is healthy",
details: {
shards: shardStatus.shards.length,
balancerRunning: balancerStatus.inBalancerRound
}
};
} catch (error) {
return {
passed: false,
message: `Failed to check sharded cluster health: ${error.message}`
};
}
}
async validateDataConsistency(source, target) {
// Implement consistency checks
// This is a simplified version - real implementation would be more thorough
const sourceCollections = await source.listCollections().toArray();
for (const coll of sourceCollections) {
const sourceCount = await source.collection(coll.name).countDocuments();
const targetCount = await target.collection(coll.name).countDocuments();
if (sourceCount !== targetCount) {
return {
passed: false,
message: `Document count mismatch for ${coll.name}: source=${sourceCount}, target=${targetCount}`
};
}
}
return {
passed: true,
message: "Data consistency check passed"
};
}
}
Production Monitoring and Maintenance
Comprehensive Monitoring Setup
# mongodb-monitoring-stack.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mongodb-exporter-config
namespace: mongodb
data:
mongodb-exporter.yml: |
mongodb:
uri: "mongodb://monitoring:password@mongos-1:27017,mongos-2:27017,mongos-3:27017/admin?replicaSet=configReplSet"
# Collect all metrics
collect_all: true
# Shard-specific metrics
collect_sharding_metrics: true
# Collect slow queries
collect_profile: true
profile_time_ts: 1000 # Queries slower than 1 second
# Collection-specific metrics
collections_to_collect:
- database: "*"
collection: "*"
indexes: true
top: true
# Custom metrics
custom_queries:
- name: "shard_distribution"
query: |
db.getSiblingDB("config").chunks.aggregate([
{ $group: { _id: "$shard", count: { $sum: 1 } } },
{ $project: { shard: "$_id", chunks: "$count" } }
])
- name: "jumbo_chunks"
query: |
db.getSiblingDB("config").chunks.find({ jumbo: true }).count()
- name: "migration_status"
query: |
db.getSiblingDB("config").changelog.find({
what: "moveChunk.from",
time: { $gte: new Date(Date.now() - 3600000) }
}).count()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongodb-exporter
namespace: mongodb
spec:
replicas: 2
selector:
matchLabels:
app: mongodb-exporter
template:
metadata:
labels:
app: mongodb-exporter
spec:
containers:
- name: mongodb-exporter
image: percona/mongodb_exporter:0.35
args:
- --config.file=/etc/mongodb-exporter/mongodb-exporter.yml
- --web.listen-address=:9216
- --log.level=info
ports:
- containerPort: 9216
name: metrics
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeMounts:
- name: config
mountPath: /etc/mongodb-exporter
volumes:
- name: config
configMap:
name: mongodb-exporter-config
---
apiVersion: v1
kind: Service
metadata:
name: mongodb-exporter
namespace: mongodb
labels:
app: mongodb-exporter
spec:
ports:
- port: 9216
targetPort: 9216
name: metrics
selector:
app: mongodb-exporter
Grafana Dashboard Configuration
{
"dashboard": {
"title": "MongoDB Sharded Cluster Dashboard",
"panels": [
{
"title": "Cluster Overview",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 0},
"targets": [
{
"expr": "mongodb_up",
"legendFormat": "{{ instance }} - {{ shard }}"
}
]
},
{
"title": "Chunk Distribution",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8},
"targets": [
{
"expr": "mongodb_chunks_per_shard",
"legendFormat": "{{ shard }}"
}
]
},
{
"title": "Operations Per Second",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8},
"targets": [
{
"expr": "rate(mongodb_opcounters_total[5m])",
"legendFormat": "{{ operation }} - {{ instance }}"
}
]
},
{
"title": "Migration Activity",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
"targets": [
{
"expr": "rate(mongodb_chunk_migrations_total[5m])",
"legendFormat": "{{ direction }} - {{ shard }}"
}
]
},
{
"title": "Query Performance",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
"targets": [
{
"expr": "histogram_quantile(0.95, mongodb_query_duration_seconds_bucket)",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.99, mongodb_query_duration_seconds_bucket)",
"legendFormat": "99th percentile"
}
]
},
{
"title": "Shard Key Efficiency",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 24},
"targets": [
{
"expr": "mongodb_shard_key_selectivity",
"legendFormat": "{{ collection }} - {{ shard_key }}"
}
]
}
]
}
}
Best Practices and Production Considerations
Shard Key Best Practices
// shard-key-best-practices.js
// Production-ready shard key patterns and anti-patterns
class ShardKeyBestPractices {
// Good shard key patterns
static goodPatterns = {
// Compound key with good cardinality
userActivity: {
key: { userId: 1, timestamp: 1 },
description: "High cardinality userId with timestamp for range queries"
},
// Hashed key for uniform distribution
documentStorage: {
key: { documentId: "hashed" },
description: "Hashed key for uniform distribution of random IDs"
},
// Location-based with additional uniqueness
geoData: {
key: { region: 1, city: 1, timestamp: 1, deviceId: 1 },
description: "Geographic distribution with time-series and unique device"
},
// Multi-tenant with tenant isolation
saasApplication: {
key: { tenantId: 1, entityType: 1, entityId: 1 },
description: "Tenant isolation with good distribution within tenants"
}
};
// Anti-patterns to avoid
static antiPatterns = {
// Monotonically increasing values
autoIncrement: {
key: { _id: 1 },
problem: "All writes go to one shard (hot shard)",
solution: "Use hashed _id or compound key"
},
// Low cardinality
status: {
key: { status: 1 },
problem: "Only few possible values, poor distribution",
solution: "Add high-cardinality field to compound key"
},
// Timestamp only
timestampOnly: {
key: { createdAt: 1 },
problem: "Creates hot shard for recent data",
solution: "Use bucketed timestamps or add another field"
}
};
// Validate proposed shard key
static validateShardKey(collection, proposedKey) {
const warnings = [];
const recommendations = [];
// Check cardinality
if (Object.keys(proposedKey).length === 1) {
const field = Object.keys(proposedKey)[0];
// Check for monotonic patterns
if (field === '_id' || field.includes('timestamp') || field.includes('date')) {
warnings.push("Single monotonic field may cause hot shards");
recommendations.push("Consider compound key or hashing");
}
}
// Check for low cardinality fields
const lowCardinalityFields = ['status', 'type', 'category', 'state'];
for (const field of Object.keys(proposedKey)) {
if (lowCardinalityFields.includes(field)) {
warnings.push(`Field '${field}' may have low cardinality`);
recommendations.push("Add high-cardinality field to improve distribution");
}
}
return {
valid: warnings.length === 0,
warnings: warnings,
recommendations: recommendations
};
}
}
// Production maintenance procedures
class ShardMaintenanceProcedures {
// Rebalance chunks across shards
static async rebalanceChunks(sh, namespace, options = {}) {
const dryRun = options.dryRun || false;
const maxChunkMoves = options.maxChunkMoves || 100;
// Get current distribution
const distribution = await sh.status();
// Calculate ideal distribution
const totalChunks = distribution.chunks.total;
const numShards = distribution.shards.length;
const idealChunksPerShard = Math.floor(totalChunks / numShards);
const moves = [];
// Identify over and under-utilized shards
const overloaded = [];
const underloaded = [];
distribution.shards.forEach(shard => {
const deviation = shard.chunks - idealChunksPerShard;
if (deviation > idealChunksPerShard * 0.1) {
overloaded.push({ shard: shard.name, excess: deviation });
} else if (deviation < -idealChunksPerShard * 0.1) {
underloaded.push({ shard: shard.name, deficit: -deviation });
}
});
// Plan chunk movements
for (const source of overloaded) {
for (const target of underloaded) {
const chunksToMove = Math.min(source.excess, target.deficit, maxChunkMoves - moves.length);
if (chunksToMove > 0) {
moves.push({
from: source.shard,
to: target.shard,
chunks: chunksToMove
});
source.excess -= chunksToMove;
target.deficit -= chunksToMove;
}
if (moves.length >= maxChunkMoves) break;
}
if (moves.length >= maxChunkMoves) break;
}
if (dryRun) {
return {
dryRun: true,
plannedMoves: moves,
estimatedTime: moves.length * 30 // Assume 30 seconds per chunk
};
}
// Execute moves
for (const move of moves) {
await sh.moveChunk(namespace, move.from, move.to);
}
return {
success: true,
movesMade: moves.length
};
}
// Handle jumbo chunks
static async splitJumboChunks(db, namespace) {
const configDB = db.getSiblingDB("config");
// Find jumbo chunks
const jumboChunks = await configDB.chunks.find({
ns: namespace,
jumbo: true
}).toArray();
const results = [];
for (const chunk of jumboChunks) {
try {
// Attempt to split the chunk
const midpoint = this.calculateChunkMidpoint(chunk.min, chunk.max);
await db.adminCommand({
split: namespace,
bounds: [chunk.min, chunk.max],
middle: midpoint
});
// Clear jumbo flag
await configDB.chunks.updateOne(
{ _id: chunk._id },
{ $unset: { jumbo: "" } }
);
results.push({
chunk: chunk._id,
success: true
});
} catch (error) {
results.push({
chunk: chunk._id,
success: false,
error: error.message
});
}
}
return results;
}
}
Conclusion
MongoDB sharding provides a robust solution for horizontal scaling of enterprise applications. Key considerations for successful implementation include:
- Careful Shard Key Selection: Choose shard keys that provide good cardinality and align with query patterns
- Zone Sharding: Implement geographic data distribution for compliance and performance
- Performance Optimization: Use connection pooling, batch operations, and query optimization
- Migration Strategy: Plan and execute zero-downtime migrations from replica sets
- Monitoring: Implement comprehensive monitoring for proactive issue detection
By following these strategies and best practices, organizations can build scalable, performant MongoDB deployments that meet enterprise requirements for availability, compliance, and performance.