Distributed indexing & search
|
This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A SolrCloud collection is only useful if a write lands on the right shard and a query can gather
results from every shard without the caller having to know the topology. This page covers how a
document is routed to a shard (the compositeId and implicit routers, composite-ID prefixes,
custom hashing, and the route query parameter), how a write then travels from the shard’s leader
out to its replicas, how a distributed search runs as two internal round trips, the
shards.tolerant and shards.preference parameters that shape that fan-out, near-real-time
visibility for a request that must see its own write, and how a replica that falls behind recovers.
The shard/replica topology itself — how many shards a collection has and how replicas are placed on
nodes — is SolrCloud architecture; creating and
configuring collections is Collections &
configsets.
Document routing
Routing answers one question for every document: which shard does this id belong to? The router is chosen once, at collection creation, and applies to every document the collection ever holds.
compositeId (the default)
With the default compositeId router, Solr hashes the document’s id field and maps the hash into
one of the shard’s hash ranges — no configuration needed, and documents spread evenly. The useful
twist is that the id itself can steer that hash: prefixing it with <prefix>! folds the prefix into
the hash calculation instead of the whole id, so every id sharing a prefix lands on the same shard.
| ID shape | Effect |
|---|---|
|
Plain id — hashed as-is, spread across all shards. |
|
Single-level prefix — every id starting |
|
Two-level prefix — the first two |
|
Prefix with a bit-count suffix — spreads one otherwise-oversized prefix across 3 bits' worth of shards instead of pinning it to one, avoiding a hot shard for a large tenant. |
# Two documents for tenant "IBM" -- the compositeId router sends both to the
# same shard because they share the "IBM!" prefix.
curl "http://localhost:8983/solr/books/update?commit=true" \
-H 'Content-Type: application/json' \
-d '[
{ "id": "IBM!1001", "title": "Annual Report 2025" },
{ "id": "IBM!1002", "title": "Quarterly Filing Q3" }
]'
# A query can then be told which shard(s) actually hold that prefix, instead
# of fanning out to all of them -- Solr resolves _route_ to the matching shard.
curl "http://localhost:8983/solr/books/select?q=*:*&_route_=IBM!"
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-shards-indexing.html
Prefix routing trades even distribution for query cheapness, exactly like Elasticsearch’s custom
routing parameter: a filtered query touches one shard instead of every shard, but a single
outsized prefix (a huge tenant) still creates a hot shard unless it is split across bits with the
/N suffix above.
implicit router and custom hashing
The implicit router does no hashing at all: shard names are assigned up front at collection
creation, and every add must say which shard it goes to, either directly with the route
parameter or, more commonly, by naming a router.field whose value is itself a shard name. This
hands routing entirely to the application — useful for time-based sharding (a router.field holding
a month or day bucket that already matches a shard name) or any scheme `compositeId’s hash can’t
express — at the cost of losing automatic rebalancing: a shard split still has to be driven by
whatever logic assigned documents to shards in the first place.
# Explicit routing with the implicit router: this add is pinned to "shard2"
# by name, bypassing any hash calculation.
curl "http://localhost:8983/solr/logs/update?commit=true&_route_=shard2" \
-H 'Content-Type: application/json' \
-d '[{ "id": "evt-9001", "message": "checkout failed" }]'
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-shards-indexing.html
route (an alias for shard.keys in older syntax) works the same way on the query side for
both routers: pass it to restrict a search to the shard(s) that own a given key instead of
scattering to the whole collection, which is the main reason to prefer prefixed ids or a
router.field scheme over plain hashing in a multi-tenant collection with skewed query patterns.
Leader → replica distributed update flow
A client can send a write to any node in the cluster — there is no dedicated "coordinator" role for indexing. That node:
-
Resolves which shard the document’s id (or
route) belongs to, using the collection’s router. -
Looks up the current leader replica of that shard in cluster state (ZooKeeper-backed) and forwards the request to it, if the receiving node is not the leader itself.
-
The leader applies the write locally, assigns it an internal version, and forwards it in parallel to every other replica of that shard, tagged with that same version so every copy converges on the same value even under concurrent/out-of-order delivery.
-
The leader waits for enough replicas to acknowledge before replying to the client. The response header’s
rf(Achieved Replication Factor) reports how many replicas actually got the write (leader included) — across a multi-shard request it is the minimum over all shards touched, so a client that cares about durability can inspect it and re-queue anything that came back degraded.
# Sent to a node that is not the leader for this shard -- Solr forwards it
# internally; the caller never has to know the topology.
curl "http://localhost:8983/solr/books/update?commit=true" \
-H 'Content-Type: application/json' \
-d '[{ "id": "IBM!1003", "title": "Sustainability Report" }]'
# rf is reported in the JSON response header, e.g. "rf":2
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-recoveries-and-write-tolerance.html
This is the same shape as Elasticsearch’s primary-forwards-to-replicas write path — the difference is naming (leader/replica vs. primary/replica shard) and that Solr elects the leader per shard via ZooKeeper rather than the cluster master assigning primaries.
Two-stage distributed search
A search sent to any node makes that node the aggregator for the request. It does not know the answer itself — it fans the query out to shards and stitches the results together in (up to) two internal round trips:
-
Query stage — the aggregator sends the query to one replica of every shard (chosen per
shards.preference, below). Each shard matches and sorts locally and returns just enough to reconstruct the global top N: document ids, sort/score values, and any data needed to merge facets or grouping — not the stored fields themselves. -
The aggregator merges those per-shard results and determines the global top N document ids.
-
Get-fields stage — a second round of internal requests asks only the shards that own those surviving ids for their stored fields (and highlighting, if requested), and the aggregator assembles the final response in order.
curl "http://localhost:8983/solr/books/select?q=title:report&rows=10"
# The aggregator handling this request runs the query stage against one
# replica per shard, then a get-fields stage against just the shards that
# own the top 10 ids.
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-distributed-requests.html
When every requested field is already available from the query stage (no highlighting, no
uninverted-only fields), distrib.singlePass=true skips the get-fields round trip entirely and
returns everything from the first pass — a latency win worth reaching for on a request that does not
need it. This two-round-trip shape is the direct counterpart of Elasticsearch’s query-then-fetch
covered in
Elasticsearch’s cluster, nodes & shards;
the mechanics differ in naming only.
shards.tolerant and shards.preference
Two request parameters shape how the fan-out above behaves when shards are unavailable or when more than one replica could answer:
| Parameter | What it controls |
|---|---|
|
Let the query stage succeed with whichever shards did respond, marking the response |
|
The stricter opposite: fail outright if the aggregator’s own ZooKeeper connection is unhealthy, rather than risk answering from stale cluster state. |
|
Orders which replica of each shard the query stage prefers, by |
# Tolerate a down shard and still get a (partial) answer.
curl "http://localhost:8983/solr/books/select?q=*:*&shards.tolerant=true"
# Prefer replicas on the aggregator's own node first, falling back to any
# other replica -- reduces cross-node network hops for the query stage.
curl "http://localhost:8983/solr/books/select?q=*:*&shards.preference=replica.location:local,replica.location:*"
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-distributed-requests.html
shards.tolerant is a request-time choice between availability and completeness — always check
partialResults in the response before trusting a result set from a query that used it.
Near-real-time visibility
The query stage above only ever sees what the target replica’s searcher currently has open, so
visibility for distributed search follows the same soft-commit/autoSoftCommit/commitWithin rules
covered in Indexing & updates — a document acknowledged
by the leader is durable immediately but not searchable on any replica, including the one that
indexed it, until that replica’s own searcher reopens. A write’s commitWithin value travels with it
through the distributed update flow, so every replica independently honors the same deadline rather
than only the leader.
# Guarantee this write is searchable across the whole shard within 2s,
# without the caller deciding hard vs. soft commit itself.
curl "http://localhost:8983/solr/books/update" \
-H 'Content-Type: application/json' \
-d '[{ "id": "IBM!1004", "title": "Risk Factors 2026", "commitWithin": 2000 }]'
An application that must read its own write before that deadline should use RealTime Get
(/get, covered in Indexing & updates) instead of
/select — it reads the transaction log directly and is unaffected by commit timing.
Node recovery and peer sync
When a replica falls behind — it was briefly down, missed updates during a network blip, or just restarted — it must catch back up before it can safely serve queries or, if it is a leader candidate, take over the shard. Recovery tries the cheap path first and only falls back to the expensive one:
-
Peer sync — the recovering replica compares its recent update history against the leader’s transaction log (the same tlog described in Indexing & updates) and replays just the missing operations. This works only when the gap is small enough that the leader’s tlog still holds every update the replica is missing.
-
Full replication — if peer sync cannot reconcile the two (the replica is too far behind, or its own index is corrupt), the replica instead pulls a complete copy of the leader’s index files, the same mechanism user-managed replication uses, and only rejoins as a live replica once that copy is in place and current.
Throughout recovery the affected replica is marked down in cluster state and receives no query
traffic (and no write traffic beyond what the leader forwards to bring it current), so a client never
sees inconsistent results from a node mid-recovery — it simply sees one fewer replica to choose from
until recovery completes. See
SolrCloud
Recoveries and Write Tolerance for leader-failover timing, the rf/Achieved Replication Factor
response header in full, and tuning how aggressively Solr retries a struggling replica before giving
up on peer sync.
Continue with SolrCloud architecture for how shard count and replica placement are decided, or User-managed mode & replication for the older, ZooKeeper-free leader/follower replication these leader/replica mechanics are modeled on.