Choosing a Shard Key

A shard key is the field — or combination of fields — that MongoDB uses to split a sharded collection’s documents across the shards in a cluster. Once sharding is enabled on a collection, every document’s shard key value decides which shard it physically lives on and which shard a future query touching that value gets routed to. Choose well, and reads and writes spread evenly across the cluster while most queries resolve on a single shard. Choose poorly, and you end up with a lopsided cluster where one shard absorbs almost all the traffic while the others sit idle — a mistake that is often expensive, sometimes practically impossible, to walk back once a collection is large.

Overview: How Shard Keys Work

A sharded cluster splits a single logical collection into chunks, each chunk covering a contiguous range of shard key values (or, for a hashed key, a range of hash values). MongoDB’s balancer distributes those chunks across the available shards, and a router process called mongos uses the shard key ranges to decide which shard(s) must be contacted for a given query, insert, or update. This is fundamentally different from a single-node deployment: the shard key isn’t just an index choice, it is the physical partitioning strategy for your data, chosen once and very costly to change later.

Three properties determine whether a candidate field makes a good shard key:

  • Cardinality — how many distinct values the field can take. A field like status with three possible values (pending, shipped, delivered) has low cardinality: MongoDB can only ever create at most a handful of chunks, so a few shards end up holding all the data no matter how large the collection grows.
  • Frequency — how evenly values are distributed. Even a high-cardinality field can be a poor choice if a small number of values dominate (e.g. a customerId field where one enterprise customer accounts for 40% of all orders). That customer’s chunk becomes a hot, oversized "jumbo chunk" that the balancer struggles to split and move.
  • Rate of change / monotonicity — whether new values trend steadily upward (or downward) over time, like _id (which embeds a timestamp), an auto-incrementing counter, or a createdAt date. With an ascending range-based shard key, every new insert lands in the same last chunk, on the same shard, until that chunk splits — and then the new last chunk absorbs all writes instead. The insert load never spreads out; one shard is permanently the write bottleneck.

The strongest shard keys are high cardinality, evenly distributed in frequency, and not monotonically increasing — and, ideally, match the fields your application already queries by most often, since queries that include the shard key can be targeted to a single shard instead of being broadcast to every shard in the cluster (a "scatter-gather" query).

Syntax

sh.shardCollection(<namespace>, <key pattern>, <unique>, <options>)
Parameter Description
namespace String "database.collection" identifying the collection to shard.
key pattern An object naming the shard key field(s), e.g. { customerId: 1 } for an ascending range key, { customerId: "hashed" } for a hashed key, or { customerId: 1, orderDate: 1 } for a compound key.
unique Optional boolean. If true, MongoDB enforces uniqueness on the shard key value (requires a matching unique index). Rarely used since it conflicts with hashed keys and compound-key flexibility.
options Optional object, e.g. { numInitialChunks: 10 } to pre-split a hashed collection, or { collation: { locale: "simple" } }.

Before sharding a collection you must run sh.enableSharding(<database>) once for the database, and the shard key field(s) must already be indexed — MongoDB creates the index automatically if the collection is empty, otherwise you must build it yourself first.

Examples

Example 1: A simple ascending shard key

use ecommerce

Output:

switched to db ecommerce
sh.enableSharding("ecommerce");

sh.shardCollection("ecommerce.orders", { customerId: 1 });

Output:

{
  collectionsharded: 'ecommerce.orders',
  ok: 1
}

This shards ecommerce.orders on customerId using an ascending range key. Chunks are ranges of customerId values; queries that filter on customerId can be routed to a single shard. Because customer IDs aren’t monotonically inserted in order, and there are many distinct customers, this is a reasonable range key — as long as no single customer dominates order volume.

Example 2: A hashed shard key for high-volume, monotonic writes

sh.shardCollection("ecommerce.events", { deviceId: "hashed" });

Output:

{
  collectionsharded: 'ecommerce.events',
  collectionUUID: UUID('3b241101-...'),
  ok: 1
}

An events collection with heavy insert traffic benefits from a hashed shard key: MongoDB stores the MD5-style hash of deviceId as the chunk boundary instead of the raw value, which scatters writes evenly across shards regardless of insertion order. The tradeoff is that range queries ("all events between two dates") can no longer target a single shard on deviceId alone, since hash order bears no relation to value order.

Example 3: A compound shard key for targeted queries and even writes

sh.shardCollection("ecommerce.orders", { customerId: 1, orderDate: 1 });

Output:

{
  collectionsharded: 'ecommerce.orders',
  ok: 1
}

A compound key leads with the high-cardinality, evenly distributed customerId field and adds orderDate as a secondary field. This keeps chunk distribution healthy (the leading field prevents monotonic hot-spotting) while letting queries that filter on both customerId and a date range use the full key for targeting, and queries that filter on customerId alone still target a single shard.

How It Works Step by Step

When a client sends a query through mongos, the router inspects the query filter against the shard key ranges it holds in its cluster metadata:

db.orders.find({ customerId: 48213 }).explain("queryPlanner");

db.orders.find({ status: "shipped" }).explain("queryPlanner");

Output (abridged):

// first query -- filter includes the shard key
winningPlan: { stage: 'SINGLE_SHARD', shards: [ 'shard0001' ] }

// second query -- filter does not include the shard key
winningPlan: { stage: 'SHARD_MERGE', shards: [ 'shard0000', 'shard0001', 'shard0002' ] }
  1. The first query includes customerId, the shard key, so mongos computes exactly which chunk (and therefore which shard) can contain matching documents and routes the query to that one shard only — a targeted query.
  2. The second query filters on status, which is not part of the shard key, so mongos has no way to rule out any shard. It fans the query out to every shard (a scatter-gather query), each shard runs it locally, and mongos merges the results before returning them to the client. This still works correctly, but it is strictly more expensive and doesn’t scale the way a targeted query does.
  3. On the write path, when a chunk grows past the configured size threshold, the shard splits it into two chunks along the shard key range. The balancer then compares chunk counts across shards and migrates chunks to keep the distribution even, all transparently to the application.

Common Mistakes

Mistake 1: An ascending, monotonically increasing shard key

// createdAt only ever increases -- all new inserts land on
// whichever shard currently owns the highest-value chunk
sh.shardCollection("ecommerce.orders", { createdAt: 1 });

This looks reasonable — createdAt is high cardinality — but every new order has a createdAt greater than every existing one, so every insert goes into the same chunk on the same shard until it splits, and then the next chunk becomes the new bottleneck. One shard absorbs essentially 100% of write traffic no matter how many shards you add.

// corrected: hash the field so insert order no longer
// determines which shard receives the write
sh.shardCollection("ecommerce.orders", { createdAt: "hashed" });

Mistake 2: A low-cardinality shard key

// status only ever has a few possible values
sh.shardCollection("ecommerce.orders", { status: 1 });

With only three or four distinct status values, MongoDB can create at most three or four chunks — far fewer than the number of shards in a large cluster — so most shards receive no data at all, and the chunks that do exist can grow into oversized "jumbo chunks" the balancer cannot split further.

// corrected: combine a high-cardinality field with the
// field you actually need to filter or group by
sh.shardCollection("ecommerce.orders", { customerId: 1, status: 1 });

Best Practices

  • Favor a field (or compound key) with high cardinality, uniform frequency, and no monotonic trend as the leading key.
  • Match the shard key to your dominant query pattern where possible, so most reads and writes become targeted, single-shard operations rather than scatter-gather.
  • Use a hashed shard key when write throughput matters more than range-scan performance, or when the only good candidate field is monotonically increasing.
  • For compound keys, put the field that best isolates queries and avoids hotspots first, and a secondary field (like a date) after it if you need range queries within a partition.
  • Treat the shard key as effectively permanent: check explain() and db.collection.getShardDistribution() against realistic data volumes before sharding in production, since changing a shard key later requires sh.reshardCollection(), an operation that copies the entire collection and can take a long time on large datasets.
  • Don’t shard prematurely — sharding adds real operational complexity, and a well-indexed single replica set handles more load than most teams expect.

Practice Exercises

  1. You have a ecommerce.reviews collection where productId has moderate cardinality (thousands of products) but a handful of best-sellers receive most of the reviews. Would { productId: 1 } alone be a safe shard key? Propose a compound key that mitigates the hot-chunk risk.
  2. Write the sh.shardCollection() call to shard ecommerce.sessions on a hashed sessionId, and explain in one sentence why a hashed key is appropriate for session data that is almost always looked up by exact sessionId.
  3. Run db.orders.find({ orderDate: { $gte: ISODate("2026-01-01") } }).explain("queryPlanner") against a collection sharded on { customerId: 1, orderDate: 1 }. Predict whether the winningPlan shows SINGLE_SHARD or SHARD_MERGE, and why.

Summary

  • The shard key determines how a sharded collection’s data is physically partitioned across shards, and it is effectively permanent once large amounts of data exist.
  • Good shard keys have high cardinality, even frequency, and are not monotonically increasing.
  • Range (ascending) keys support efficient range queries but risk hot shards if values trend upward; hashed keys spread writes evenly but sacrifice efficient range scans.
  • Queries that include the shard key are targeted to one shard; queries that don’t are scattered to every shard and merged by mongos.
  • Use explain() to confirm whether a query is SINGLE_SHARD or SHARD_MERGE, and evaluate a candidate shard key against realistic production data volumes before committing to it.