Sharding in MongoDB: a practical MongoDB & NoSQL guide
A single replica set can only scale vertically—add more CPU, RAM, disk. But at some point, one server becomes the bottleneck for either data volume or write throughput. Sharding in MongoDB: a practical MongoDB & NoSQL guide splits your data across multiple servers, each holding a subset, so queries and writes distribute in parallel. Get the shard key wrong, though, and you'll spend weeks fixing it.
When to shard — Data or write load exceeds one replica set
Sharding is not free. It adds operational complexity, makes transactions harder, and introduces latency through routing. You reach for it only when a single replica set can no longer keep up.
Consider these signals:
- Data size: Your working set no longer fits in RAM. Disk I/O becomes the limiter.
- Write throughput: One primary node hits 80%+ CPU or storage IOPS ceilings. Replication lag grows. You're applying the same write to all secondaries and the bottleneck is single-threaded.
- Read throughput: You've scaled reads across secondaries, but queries still time out or CPU stays maxed.
- Operational burden: Recovery after a primary failure takes too long because the dataset is huge.
In production e-commerce systems we've scaled, sharding usually enters the conversation when collections exceed 100–500 GB and write load hits 10,000–50,000 ops/sec on a single primary. Until then, vertical scaling and read replicas are cheaper and simpler.
One common mistake: sharding too early. A well-tuned three-node replica set on modern hardware can absorb far more than teams expect. Benchmark first. Profile indexes. Only shard when you have hard data.
Shard key — The most important decision — hard to change later
The shard key is the field (or fields) MongoDB uses to decide which shard receives each document. Once you shard a collection, changing the shard key is extremely painful: you must dump and re-import data, or use the resharding feature (available since MongoDB 4.4, still resource-intensive).
Choose wrong and you'll live with hot shards, uneven data distribution, or inefficient queries for years. Get it right and sharding feels invisible.
MongoDB routing works like this: hash or range the shard key value, compute a target shard, send the request there. If the query includes the shard key, MongoDB can target one or a few shards. If it doesn't, every shard must be queried in parallel (a "scatter-gather").
// A collection of orders
db.orders.createIndex({ customerId: 1 })
db.adminCommand({
shardCollection: "myapp.orders",
key: { customerId: 1 }
})
// Query with the shard key — routed to one shard
db.orders.find({ customerId: 12345 })
// Query without the shard key — scatter-gather, slow
db.orders.find({ status: "pending" })
Your choice of shard key sets the tone for how efficiently your cluster will operate. Invest time in this decision.
Good keys — High cardinality, even distribution, matches query patterns
A good shard key has three properties.
High cardinality
Cardinality is the number of unique values. A shard key with low cardinality (e.g., country, with ~200 values) means many documents hash to the same shard, defeating the purpose of sharding. A field like user ID or order ID has millions or billions of unique values—high cardinality.
Even distribution
Even if cardinality is high, the actual distribution matters. If 70% of your users are in one country and that's part of your shard key, one shard will hold 70% of the data. You've not solved the problem; you've just moved it.
In a ride-hailing system, sharding by city creates obvious hot spots: New York and San Francisco get far more rides than rural towns. Sharding by ride ID (UUID or similar) distributes evenly instead.
Matches query patterns
Your most frequent or slowest queries should include the shard key. If 80% of traffic is db.orders.find({ customerId: X, createdAt: Y }), then customerId is an excellent shard key—it targets one shard and lets you run that query fast.
In a SaaS CRM, queries often filter by tenant ID and creation date. Sharding by { tenantId: 1, createdAt: 1 } (a compound shard key) targets the correct shard and keeps related data together.
// Sharding strategy for a multi-tenant CRM
db.contacts.createIndex({ tenantId: 1, createdAt: 1 })
db.adminCommand({
shardCollection: "crm.contacts",
key: { tenantId: 1, createdAt: 1 }
})
// Query is routed efficiently because it includes the shard key
db.contacts.find({
tenantId: "acme-corp",
createdAt: { $gte: ISODate("2024-01-01") }
})
Bad keys — Monotonic keys like timestamps create hot shards
A bad shard key is one that skews writes to a small subset of shards. The classic culprit is a monotonic key—one that only increases or decreases.
Timestamps
If you shard by createdAt, all inserts go into the same shard until the timestamp rolls into the next chunk range, then they shift to the next shard. For a window of time, one shard absorbs all writes. The others sit idle. When queries filter by timestamp (common in time-series data), you scatter across many shards unnecessarily.
Sequential IDs
ObjectIds are technically not monotonic—they embed timestamp + machine ID + counter—but if you generate them yourself with a counter (1, 2, 3, ...), you've created a monotonic key. All new documents go to one shard.
Real-world damage
In a trading platform, if you shard by trade timestamp, 100% of writes during market hours land on one shard: write throughput becomes bottlenecked, latency soars, and you gain nothing from sharding. Your "scaled" cluster still has a single point of write contention.
Better: shard by { accountId: 1, tradeTimestamp: 1 }. Account ID has high cardinality and even distribution; trades from different accounts spread across shards in parallel.
// Bad: all writes to one shard at a time
db.trades.createIndex({ tradeTimestamp: 1 })
db.adminCommand({
shardCollection: "trading.trades",
key: { tradeTimestamp: 1 } // DON'T DO THIS
})
// Good: trades spread across shards by account
db.trades.createIndex({ accountId: 1, tradeTimestamp: 1 })
db.adminCommand({
shardCollection: "trading.trades",
key: { accountId: 1, tradeTimestamp: 1 }
})
Common mistakes
- Choosing a shard key based on future queries you haven't measured yet. Profile your production workload first. Use MongoDB's query profiler and monitoring. Shard based on how your app actually behaves, not how you think it will.
- Forgetting about range-based insertion patterns. If 90% of your new data arrives from a specific source or region, and that's baked into your shard key, you'll create a hot shard. Randomize or hash to break the pattern.
- Using the shard key in sort operations without an index. MongoDB can push sorting to shards if the shard key is first in the compound index. Misalign your index and queries become slow.
- Assuming balanced chunk distribution means balanced load. Chunks can be evenly sized, but if one shard has more active documents (e.g., users who generate lots of writes), that shard will work harder. Monitor CPU and I/O, not just chunk count.
- Sharding without proper monitoring. Set up dashboards to track chunk migration, shard utilization, and scatter-gather queries. A cluster can look healthy while silently suffering.
Key takeaways
- Shard only when a single replica set reaches its limits: data size, write throughput, or read load overwhelm the hardware.
- The shard key is permanent—change it later at enormous cost. Invest days in analysis before committing.
- Good shard keys have high cardinality, distribute data evenly, and align with your most frequent queries.
- Avoid monotonic keys (timestamps, sequences) that funnel writes to a hot shard and defeat the purpose of sharding.
- Compound shard keys can improve query targeting and data co-location; they're often worth the added complexity.
- After sharding, monitor chunk migration, shard utilization, and query patterns. Clusters evolve; revisit your strategy annually.






Comments
Loading comments…