sehaj/learn
System design/10 Search

10.04 · concept · 10 min

Sharding a search cluster

Spreading a search index over many machines: splitting by document, scatter-gather queries, why one slow shard sets the latency, routing a tenant's searches to one shard, sizing shards, and replicas for throughput.

Snip's search index covers every link: 600 million documents, about 600 GB of index. One machine can't hold it in memory, and can't serve the agencies' query rate alone. The index has to be split.

Split by document, not by term

There are two ways to cut an inverted index:

  • By term: one machine owns terms A–F, another G–M. A query touches only the machines owning its terms, but multi-word queries cross machines to intersect, and popular terms make hot machines.
  • By document: each shard holds all terms for a subset of documents, a complete small index. A query goes to every shard; each finds and scores its own matches.

Search engines almost always split by document: each shard is independent, and adding documents or shards is simple. In Elasticsearch and OpenSearch each shard is a complete Lucene index.

Scatter, gather, then fetch

A query against a document-sharded index runs in two phases:

  1. Query: the coordinating node sends the query to one copy of every shard. Each returns the IDs and scores of its own top 10.
  2. Fetch: the coordinator merges the lists into the global top 10, then fetches only those 10 documents from the shards that hold them.

Asking each shard for its top 10 is enough for the global top 10, since every document in the global top 10 is in its own shard's top 10. But page 50 (results 491–500) needs each shard's top 500, so deep pagination gets expensive fast. Use search-after cursors, as in keyset pagination (05.07 Keyset pagination), and cap how deep users can go.

The slowest shard sets the pace

A query fanned out to 20 shards waits for all 20 (03.12 Tail latency and why p99 matters). If each shard is slow 1% of the time, about 18% of queries hit at least one slow shard. Defences: keep shards evenly sized, keep each shard's hot data in memory, use replicas so a slow copy can be avoided, and route around busy nodes. Elasticsearch does the last by default with adaptive replica selection, which picks a copy using the response time of earlier requests and the size of each node's search queue.

Route a tenant's searches to one shard

Snip's searches are always within one owner's links. Fanning every query out to all 20 shards wastes 19 of them. With custom routing, each document is indexed with a routing value, here owner_id, which decides its shard, and a search that passes the same routing value runs only on that shard.

Fan out to every shardRoute by owner
Shards per query201
Tail latencySlowest of 20One shard's
ThroughputEvery query costs every shardEach query costs one shard
RiskNone from tenantsA huge owner makes a hot, oversized shard

Routing by owner is a big win for Snip, with the usual catch: the largest agency has 40 million links, far more than its share. Like any hot key (05.02 Hot partitions and celebrity keys), it gets special handling: very large owners get their own index, sharded across machines, while the long tail of small owners shares routed shards.

Sizing shards

  • Not too big. Elastic's guidance is to aim for shards of 10 GB to 50 GB, and fewer than 200 million documents each. Big shards are slow to move and recover.
  • Not too many. Each shard is a separate Lucene index with its own memory and CPU overhead, and every unrouted query touches all of them. Thousands of tiny shards waste resources.
  • Replicas serve reads and survive node failure. Two replicas triple the storage and roughly triple read capacity.

For Snip's 600 GB: 20 primary shards of about 30 GB, each with one replica, plus separate indexes for the three largest agencies.

Seen in production

Elastic

Docs

Shard sizing and routing

Elastic recommends shard sizes between 10 GB and 50 GB and fewer than 200 million documents per shard. Each shard is a separate Lucene index, and every index and shard needs some memory and CPU. Its search shard routing documentation describes routing a document to a specific shard with a routing value, so a search with the same value runs on that shard, and adaptive replica selection, which picks copies by response time and search queue size to reduce latency.

What we foundShards of 10–50 GB and under 200 million documents; each one is a Lucene index with its own overhead.

Size your Elasticsearch shards (Elasticsearch documentation)elastic.co ↗

GitHub

2023

Code search shards

GitHub shards its code search index by Git blob object ID, which spreads documents evenly between shards and avoids duplication. It reports p99 response times from individual shards on the order of 100 ms, with total response times a bit longer.

What we foundShard by content hash for even spread; per-shard p99 around 100 ms, with the whole query a bit slower.

The technology behind GitHub's new code search (Timothy Clem)github.blog ↗
Predict first

Snip routes searches by owner_id. One agency with 40 million links now has slow searches, and its shard's node runs hot while others are idle. What's the best fix?

Commit to an answer before you read on.

Recap

Interview questions

SeniorfreqGood to knowConcept

How does a distributed search engine execute a query across shards?

Report a mistakeEdit this page
☰ Search10.04 · 83/147next →