A single MongoDB instance (or replica set) scales vertically: more CPU, more RAM, faster disks. This works until the working set exceeds available memory, write throughput saturates a single primary, or the dataset outgrows what a single machine can store cost-effectively. Vertical scaling has a hard ceiling set by available hardware, and cloud instance pricing is non-linear near the top of any given tier.
Horizontal scaling — sharding — partitions a dataset across multiple servers instead of growing one server. Each shard holds a subset of the data and handles a subset of the query load. Storage capacity and throughput scale roughly linearly with the number of shards, provided the data is distributed evenly.
Replication and sharding solve different problems. Replica sets provide redundancy and failover — every node holds the full dataset. Sharding provides scale — each shard holds only part of the dataset. Production sharded clusters combine both: every shard is deployed as its own replica set.
A sharded cluster has three component types:
configReplSet), typically three members.mongos, never directly to shards. It consults the config servers to route or broadcast operations and merges results before returning them to the client.MongoDB shards data at the collection level, not the database or deployment level. Unsharded collections in a sharded database live entirely on one shard — the database's primary shard — unless explicitly moved or sharded.
The shard key is one or more fields present in every document of a sharded collection, chosen at the time the collection is sharded. It determines how documents are distributed across shards. This is the single most consequential decision in a sharding deployment — as of MongoDB 5.0, an existing shard key can be changed via reshardCollection, but doing so is a heavyweight background operation, not a quick fix.
Three properties determine whether a shard key produces even distribution:
| Property | What it means | Failure mode if ignored |
|---|---|---|
| Cardinality | Number of distinct possible values | Low-cardinality keys (e.g. status with 3 values) cap the number of chunks that can ever exist |
| Frequency | How evenly values repeat across documents | A frequently repeated value pins many documents to one chunk, which cannot split further |
| Monotonicity | Whether values increase/decrease predictably (timestamps, ObjectIds, auto-increment IDs) | All new writes target the same chunk and therefore the same shard — a write hotspot |
A monotonically increasing shard key — _id with default ObjectIds, or a created_at timestamp — routes every insert to the same chunk until it splits and migrates. Under sustained write load this creates a permanent hotspot on whichever shard owns the current high end of the range.
MongoDB supports two shard key strategies:
// enable sharding on the database
sh.enableSharding("app_db")
// ranged shard key on a compound field
sh.shardCollection(
"app_db.orders",
{ region: 1, order_id: 1 }
)
// hashed shard key — avoids monotonic write hotspots
sh.shardCollection(
"app_db.events",
{ device_id: "hashed" }
)
A compound shard key — combining a low-cardinality prefix with a high-cardinality, non-monotonic suffix — is often the practical middle ground: it preserves some range-query locality while avoiding the collapse into a single hot chunk.
Sharded collections are internally divided into chunks — contiguous ranges of shard key values. Each chunk is assigned to exactly one shard. As data is inserted, chunks that exceed the configured size threshold split into two. The balancer, a background process coordinated through the config servers, monitors chunk counts per shard and migrates chunks from over-loaded shards to under-loaded ones to keep the distribution even.
Chunk migration is a background, non-blocking operation from the application's perspective, but it consumes shard I/O and network bandwidth. On clusters under heavy sustained write load, migrations competing with ingestion is a common source of latency spikes — a reason production deployments often schedule balancer windows during low-traffic periods.
A jumbo chunk is a chunk that has grown past the split threshold but cannot be split further, because the documents inside it all share the same shard key value (or fall within a range too narrow to divide). Jumbo chunks cannot migrate and permanently pin their data to one shard. They are almost always a symptom of a low-cardinality or high-frequency shard key.
// cluster-wide shard and chunk summary
sh.status()
// per-shard document and size distribution for one collection
db.orders.getShardDistribution()
// current balancer state
sh.getBalancerState()
Zones (formerly "tag ranges") associate one or more shard key ranges with a specific set of shards. This is used to pin data to particular hardware — for example, routing EU customer data to shards physically located in an EU region for data-residency requirements, or isolating a tenant's data onto dedicated shards in a multi-tenant deployment.
sh.addShardToZone("shard-eu-01", "EU")
sh.updateZoneKeyRange(
"app_db.customers",
{ region: "eu-west", customer_id: MinKey },
{ region: "eu-west", customer_id: MaxKey },
"EU"
)
Not every collection needs to be sharded. A collection stays fully functional and queryable through mongos while unsharded — it simply lives entirely on the database's primary shard. Current guidance favors deploying a sharded cluster from the start, even with a single shard, specifically to keep this option open without a later migration.
The moveCollection command relocates an unsharded collection to a different shard as an online operation, without disrupting application reads or writes. This is useful for isolating a noisy collection onto its own shard, separating tenants, or rebalancing storage — all without the overhead of sharding the collection itself.
db.adminCommand({
moveCollection: "app_db.audit_log",
toShard: "shard-cold-01"
})
Reasons to move rather than shard a collection: isolating a workload to stop it competing for I/O with others on the same shard (the "noisy neighbor" problem), satisfying geographic data placement rules, and cost optimization — moving low-access collections onto cheaper hardware tiers.
MongoDB 8.0 introduced config shards, which collapse the dedicated config server replica set into one of the existing shard replica sets, rather than requiring separate infrastructure. In MongoDB Atlas, sharded clusters with up to three shards on 8.0 use config shards by default. This lowers the infrastructure floor for starting a sharded cluster — historically a deterrent to sharding early, given the extra replica set to provision and operate.
Because config shards remove the dedicated-infrastructure cost of a sharded cluster's metadata layer, starting new applications on a single-shard cluster from day one is now a low-overhead way to avoid an unsharded-to-sharded migration later, without paying for a standalone config server replica set upfront.
Changing a shard key after a collection is sharded used to require exporting and reimporting the entire dataset. Since MongoDB 5.0, reshardCollection performs this in place: it clones the collection under the new shard key in the background, keeps it in sync with ongoing writes, then atomically cuts over.
db.adminCommand({
reshardCollection: "app_db.events",
key: { device_id: "hashed" }
})
This is still a heavyweight operation on large collections — it duplicates the collection's storage footprint for the duration of the resync and adds sustained load to every shard involved. It's a recovery path for a bad shard key choice, not something to run casually.
How mongos routes a query depends on whether the query includes the shard key:
mongos merging results. These scale with the number of shards in the wrong direction: more shards means more round trips per query, not fewer.Secondary indexes that don't include the shard key still require a scatter-gather query to enforce uniqueness or serve lookups, because no single shard can guarantee it holds the only matching document. Index design in a sharded collection has to account for the shard key, not just query patterns in isolation.
Current guidance is less "shard when you hit a wall" and more "shard proactively." Concrete triggers for sharding a specific collection:
None of these need to be true simultaneously, and none of them require sharding every collection in a database — collection-level granularity means the decision can be made per collection, with the rest staying unsharded on the primary shard.