An index in the wild is cut into pieces and spread across many computers, yet a search must still feel like it asked one machine. This essay is about the arithmetic of that illusion: how one query fans out and merges back, why scores and counts can quietly lie in the merge, which copy of the data answers, and why page 1,000 costs a thousand times more than page one.
An index in the wild is sharded: cut into pieces, each a fully working index of its slice, spread across machines. Any node can receive your query and becomes its coordinator: it broadcasts to one copy of every shard, each shard ranks its own documents and returns only its few best candidates, just IDs and scores, and the coordinator merges them and fetches full text for the winners only.
The figure below holds one search, frozen in time. The timeline slider is time: drag it slowly through the request's life, backwards too. Then change the shard count and drag through it again:
The elegance is in what doesn't move: a query over a billion documents ships a few hundred bytes each way. The heavy work happens where the data lives, in parallel. Add shards and the same search gets faster, at the price of a slightly busier merge, a trade you just measured yourself.
There is a subtle bug hiding in "each shard ranks its own slice." BM25 weighs terms by rarity, and rarity is computed from the documents a shard can see. If one shard happens to hold most of the documents mentioning kubernetes, that term looks common there and exotic on its neighbor, and identical documents get different scores depending on where they landed. Usually the shards are big enough that the statistics agree; when they don't, one round-trip fixes it:
Every shard exists in copies, so every fan-out faces a choice: which replica answers? Round-robin is fair and blind; it keeps sending queries to a node that is busy garbage-collecting. Elasticsearch instead scores each copy by its recent response times, queue depth, and service time, and sends traffic where the wait is shortest: adaptive replica selection. Both strategies below face the same three nodes, one of which degrades on and off:
Distributed reading has a trap built into its arithmetic. To serve results 10 through 20, the coordinator cannot ask each shard for "your share": it must fetch each shard's entire top 20, because any shard might own all ten winners. Now ask for page 1,000:
from + size at 10,000 by default. The escape is search_after: remember the sort values of the last result you saw (inside a point-in-time, so the pages stay consistent), and ask each shard only for what comes after them, ten per shard, forever, at page one prices.Aggregations fan out too, and here the merge can quietly lie. Ask for the top 3 error codes: each shard sends its local top 3, the coordinator sums them. But a term that is fourth place everywhere is never sent by anyone, and a term that is merely hot on one shard looks like a global champion. This dataset is rigged to break it:
auth_fail, and cache_miss, the true champion at 222, does not appear at all: it is fourth on every shard, so no shard ever mentions it. The blue bar is what the coordinator counted, the gray bar the truth. Widen shard_size and watch the truth assemble. Elasticsearch defaults shard_size to 1.5 × size + 10, and the response carries doc_count_error_upper_bound, computed as in the last line: an unsent term can have no more, on each shard, than the smallest count that shard did send. It is the honest confession of how wrong the counts could be.The partial-then-merge pattern you have now seen three times, in scores, in pages and in counts, reappears inside ES|QL's compute engine; what happens when these machines fail, elections, replicas, rebalancing, is The cluster.