Local vs global secondary indexes in a sharded database: an interactive simulator
A sharded table can only find rows quickly by the key it is sharded on. To look rows up by anything else you need a secondary index, and it can be sharded two ways. A local index lives next to the data on each shard, so writes stay on one shard but every query must ask all of them. A global index is sharded by the indexed value, so a query asks one index shard, but every write now touches two shards and the index usually lags behind.
The playground looks up orders by customer on a table sharded by order id. Each shard usually answers in about 2 ms, but 1% of requests hit a stall of 20 to 60 ms.
16 shards, orders sharded by order id, 2,000 “orders for this customer” queries a second returning one order, and 2,000 new orders a second. A local index makes every query ask all 16 shards: 34,000 shard requests a second, and 15% of queries wait on at least one shard that happens to be slow. A global index asks one index shard and then the one shard holding the order: 8,000 requests a second and 2% slow queries. The global index pays on the write side: every new order also updates the customer’s index shard, usually asynchronously, so a customer may not see an order they placed for about 200 ms.
- Shards per query
- 16
- Shards per write
- 1
- Shard requests
- 34,000/s
- Query p50 / p99
- 3.8 / 60 ms
- Delayed by a slow shard
- 15%
- Index freshness
- same transaction
- Shards per query
- 2
- Shards per write
- 2
- Shard requests
- 8,000/s
- Query p50 / p99
- 4.1 / 41 ms
- Delayed by a slow shard
- 2%
- Index freshness
- ~200 ms behind
What the trade-off looks like
- Local indexes scatter reads. At 16 shards every query is 16 shard requests, 34,000 a second for 2,000 queries plus the writes, and 15% of queries wait on at least one slow shard. At 64 shards it is 130,000 requests a second and nearly half of all queries are delayed: adding shards makes these queries slower and more expensive, not faster.
- Global indexes keep reads narrow. One index shard and the shard holding the order: 2 requests per query whatever the cluster size, and only 2% of queries delayed.
- But writes pay. Each new order also writes the customer’s index shard, a second request, often on another machine. Doing that in the same transaction needs a distributed commit; most systems update the index asynchronously, so a query can miss an order written moments ago.
- Big results erode the advantage. If a query returns 20 orders spread across the cluster, a global index still has to fetch them from up to 20 shards. With only 4 shards that is no better than asking all of them, which is why many designs store the needed fields in the index itself.
Where you meet them
-- local (document-partitioned): each shard indexes its own rows
-- Elasticsearch, MongoDB, Cassandra secondary indexes, DynamoDB local secondary indexes
SELECT * FROM orders WHERE customer_id = 812; -- scatter to every shard, gather
-- global (term-partitioned): the index is its own table, sharded by the indexed value
-- DynamoDB global secondary indexes, Spanner and CockroachDB indexes
orders_by_customer: PK (customer_id, order_id) -> order shard
SELECT order_id FROM orders_by_customer WHERE customer_id = 812; -- one shard
-- then fetch those orders by id from wherever they liveIn a system design interview
When a sharded design needs a second access path, say which kind of index it is: “Orders are sharded by order id, and the order history page queries by customer, so we keep a global index sharded by customer id that includes the fields the page shows. It is updated asynchronously from the change stream, so a new order can take a fraction of a second to appear; the confirmation page reads the order by id instead. Rare admin searches by status can scatter-gather across shards.” Expect the follow-ups: what happens to the index when a write fails halfway, and whether the data could be sharded by customer in the first place.
Read the guide to sharding and partitioning or database indexing, see choosing a shard key and tail latency, or watch a strong candidate design DoorDash.
Questions people ask
What is the difference between a local and a global secondary index?
A local, or document-partitioned, index is stored on each shard and covers only that shard’s rows, so queries must ask every shard. A global, or term-partitioned, index is sharded by the indexed value, so a query asks one index shard, but each write updates the base shard and an index shard.
Why are scatter-gather queries slow?
A query sent to every shard finishes only when the slowest shard answers, so its latency is the worst of many. Even if each shard is rarely slow, the chance that at least one of dozens is slow is high, and the total work grows with the number of shards.
Are global secondary indexes consistent?
Often not immediately. Updating the index shard in the same transaction as the base row needs a distributed commit, which databases such as Spanner do; many others, such as DynamoDB, update global indexes asynchronously, so recent writes can be missing from index queries briefly.
What are DynamoDB LSIs and GSIs?
A local secondary index shares the partition key with the table and indexes within each partition; a global secondary index has its own partition key, so it can serve queries on other attributes across the table, is updated asynchronously and has its own capacity.
When is a local index better?
When queries usually include the shard key, when the cluster has few shards, or when writes dominate and must stay on one shard and consistent. Scatter-gather is also acceptable for rare, latency-tolerant queries such as admin searches.
How do you avoid fetching rows from many shards after a global index lookup?
Store the fields the query needs in the index entries themselves, a covering or projected index, so the index shard can answer alone. It costs more storage and write work but turns a two-step lookup into one.
Should I shard by the field I query by instead?
If one access path dominates, shard the data by it so those queries hit one shard and no secondary index is needed. Secondary indexes are for the other access paths, and some systems keep a second copy of the data sharded the other way.