Choosing the Right Shard Key
The shard key is the single most important decision in your sharding design. A bad shard key can make your sharded database perform worse than a single server. A good one can scale you to millions of users.
Once you choose a shard key and have data in production, changing it typically requires a complete data migration. Take time to get this right from the start.
What Makes a Good Shard Key?
A good shard key has four essential properties:
Many unique values to spread across shards
Values are uniformly accessed
Matches your query patterns
Value doesn't change over time
Property 1: High Cardinality
Cardinality is the number of unique values. Higher cardinality = more potential shards.Low Cardinality (Bad):
status: ["active", "inactive", "pending"] // Only 3 values
High Cardinality (Good):
user_id: [1, 2, 3, ... 100,000,000] // Millions of values
Low Cardinality: status
Only 3 possible values means maximum 3 shards. 90% of users might be "active", creating a massive hotspot.
High Cardinality: user_id
100 million unique values. With hashing, users distribute evenly across any number of shards.
Property 2: Even Distribution
High cardinality isn't enough. The values must also be evenly accessed.
Twitter shards tweets by user_id. When Elon Musk tweets (300M followers), one shard handles millions of requests. High cardinality, but uneven access = hotspot.
Write Distribution vs Read Distribution
Consider both:
# User sign-ups (writes)
user_id: Even distribution ✓
# User activity (reads)
user_id: Uneven! Active users accessed 1000x more than dormant users
# Solution: Combine with hashing or accept some hotspots
Property 3: Query Alignment
Your shard key should match your most common queries. If queries don't include the shard key, you'll need to query all shards (scatter-gather).
# If shard key is user_id:
# GOOD: Query includes shard key - goes to one shard
SELECT * FROM orders WHERE user_id = 12345;
# BAD: Query doesn't include shard key - must query ALL shards
SELECT * FROM orders WHERE order_date = '2024-01-15';
Single-Shard Query
Query: WHERE user_id = 123
Router knows: user_id 123 is on Shard 2
Query 1 shard, get result
Scatter-Gather Query
Query: WHERE order_date = '2024-01-15'
Router doesn't know which shard has the data
Query ALL shards, merge results
Property 4: Immutability
The shard key value should never change. If it does, the record must move to a different shard.
# BAD: Mutable shard key
shard_key = user.country # User moves from "US" to "UK"
# Now user's data needs to migrate from US shard to UK shard!
# GOOD: Immutable shard key
shard_key = user.id # Never changes
Common Shard Key Patterns
Pattern 1: Entity ID
The most common pattern. Shard by the primary entity ID.
# E-commerce: Shard by user_id
orders = {
"shard_key": "user_id",
# All of a user's orders on same shard
}
# Messaging: Shard by conversation_id
messages = {
"shard_key": "conversation_id",
# All messages in a conversation together
}
Applications where queries are scoped to a single user/entity. "Show me my orders", "Show messages in this chat".
Pattern 2: Compound Keys
Combine multiple fields for better distribution and query flexibility.
# Multi-tenant SaaS
shard_key = (tenant_id, user_id)
# Benefits:
# 1. Queries with tenant_id go to subset of shards
# 2. Queries with tenant_id + user_id go to single shard
# 3. Large tenants spread across multiple shards
-- MongoDB compound shard key
sh.shardCollection("db.users", { tenant_id: 1, user_id: 1 })
-- Queries that work efficiently:
{ tenant_id: "acme" } -- Subset of shards
{ tenant_id: "acme", user_id: "u123" } -- Single shard
Pattern 3: Hashed ID
Hash the ID to ensure even distribution (especially for sequential IDs).
# Auto-increment IDs are sequential
user_id = 1, 2, 3, 4, 5...
# Hash them for even distribution
shard_key = hash(user_id)
# Now sequential IDs distribute randomly across shards
hash(1) % 4 = 2
hash(2) % 4 = 0
hash(3) % 4 = 3
hash(4) % 4 = 1
Anti-Patterns: What to Avoid
Timestamp as Shard Key
All new writes go to the "current time" shard. Creates massive write hotspot.
Low Cardinality Field
Limited values mean limited shards and uneven distribution.
Monotonically Increasing
Sequential IDs with range sharding = all writes to last shard.
Mutable Fields
If the shard key changes, data must migrate between shards.
Real-World Case Study: Instagram
Instagram shards user data by user_id, but for the media table, they use a compound approach:
Instagram generates IDs that embed the shard information directly. Their IDs contain: timestamp (41 bits) + shard ID (13 bits) + sequence (10 bits). This way, the ID itself tells you which shard the data is on - no lookup needed.
# Instagram-style ID generation
def generate_id(shard_id):
timestamp = current_time_ms() - EPOCH
sequence = get_next_sequence()
# 41 bits timestamp | 13 bits shard | 10 bits sequence
id = (timestamp << 23) | (shard_id << 10) | sequence
return id
# ID: 1387262464000 encodes shard 42
# You can extract shard_id directly from any ID
Decision Framework
Use this checklist when selecting a shard key:
| Question | What You Want |
|---|---|
| How many unique values? | At least 10x your target shard count |
| Are writes evenly distributed? | No single value should get >10% of writes |
| Do most queries include this key? | 80%+ of queries should include the shard key |
| Does this value ever change? | Never (or extremely rarely) |
| Will there be hotspots? | Identify and plan for known hotspots |
Key Takeaways
- High cardinality - many unique values for proper distribution
- Even distribution - both data and access patterns should be uniform
- Query alignment - most queries should include the shard key
- Immutable - shard key values should never change
- Compound keys can provide better distribution and query flexibility
Next up: Rebalancing - What happens when you need to add or remove shards.