SolrCloud architecture

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.

SolrCloud is what turns a set of independent cores into one coordinated collection: an external Apache ZooKeeper ensemble holds the cluster’s shared state, one Solr node is elected Overseer to serialize collection-management changes, and each shard elects its own leader among its replicas. This page covers that coordination layer end to end — what ZooKeeper actually stores, how the Overseer and shard leaders are chosen, the three replica types and when to pick each, and how to bring a cluster up and load its configuration.

Collections, shards, replicas, cores

The vocabulary is unchanged from Cores vs. collections: a collection is the logical, named index your application queries; it is split into shards, each shard holds a distinct slice of the documents, and each shard is served by one or more replicas. Every replica — leader or not — is physically an ordinary core, auto-named like books_shard1_replica_n1, living inside some Solr node’s data directory. What SolrCloud adds on top of that vocabulary is everything below: a ZooKeeper-backed registry of which cores exist and where, and an election protocol that picks which replica of each shard is currently allowed to accept writes.

ZooKeeper: the cluster’s source of truth

Every SolrCloud node connects to the same ZooKeeper ensemble and keeps a local, continuously-updated watch on its znodes rather than polling. Broadly:

znode What it holds

/live_nodes

An ephemeral entry per running Solr node, created on connect and removed automatically (by ZooKeeper’s own session expiry) the instant that node disconnects — this is how the rest of the cluster learns a node is gone.

/collections/<name>/state.json

The authoritative cluster state for one collection: its shards, every replica’s core name, node, replica type, and which replica is the current leader.

/configs/<name>

A configset — solrconfig.xml, the schema, and supporting files — stored centrally so every replica of every collection that references it loads the identical configuration.

/overseer, /overseer_elect

The Overseer’s work queue and the ephemeral nodes used to elect it.

Because configsets live in ZooKeeper rather than on each node’s local disk, adding a node or replacing a dead one needs no manual file copy: the new replica simply reads its collection’s configset out of ZooKeeper on startup. Collections API, configsets & replica placement covers configset reuse, versioning, and the CREATE/RELOAD collection actions that read from it.

A production ensemble should run an odd number of ZooKeeper servers — 3 tolerates one failure, 5 tolerates two — because it needs a strict majority (quorum) alive to keep serving writes; running only 1 or 2 gives up either redundancy or availability for no benefit. bin/solr start -e cloud launches a disposable, embedded single-node ZooKeeper for local trials only — point at a real external ensemble for anything that must survive a ZooKeeper node failing.

# Start Solr in cloud mode against an external 3-node ensemble, with a chroot path
# so several Solr clusters can share one ensemble under separate znode subtrees.
bin/solr start -e cloud -z zk1:2181,zk2:2181,zk3:2181/solr

# The chroot path (/solr) must already exist as a znode unless you opt in to creating it:
ZK_CREATE_CHROOT=true bin/solr start -e cloud -z zk1:2181,zk2:2181,zk3:2181/solr

# Avoid repeating -z on every command: set it once in the include file instead.
echo 'ZK_HOST="zk1:2181,zk2:2181,zk3:2181/solr"' >> bin/solr.in.sh
# https://solr.apache.org/guide/solr/latest/deployment-guide/zookeeper-ensemble.html

Upload a configset before creating any collection that references it, using either the bin/solr zk subcommand or the lower-level zkcli.sh it wraps:

# bin/solr zk: upconfig pushes a local directory into /configs/<name>.
bin/solr zk upconfig -z zk1:2181,zk2:2181,zk3:2181/solr -n books -d server/solr/configsets/_default/conf

# zkcli.sh is the same operation, spelled out explicitly -- useful in scripts that
# already invoke it directly rather than through bin/solr.
server/scripts/cloud-scripts/zkcli.sh -zkhost zk1:2181,zk2:2181,zk3:2181/solr \
  -cmd upconfig -confname books -confdir server/solr/configsets/_default/conf

# Now the collection can reference the uploaded configset by name.
bin/solr create -c books -s 2 -rf 3 -n books
# https://solr.apache.org/guide/solr/latest/deployment-guide/zookeeper-ensemble.html

The Overseer

Collection-management operations — creating a collection, adding or deleting a replica, splitting a shard — are not applied directly by whichever node receives the API call. They are queued as messages in /overseer/queue, and exactly one node in the cluster, the Overseer, dequeues and applies them one at a time, writing the result into each collection’s state.json. Serializing every such change through one node is what keeps two concurrent ADDREPLICA calls from racing each other into an inconsistent cluster state.

The Overseer itself is just another elected role, using the same ZooKeeper ephemeral-node election mechanism as shard-leader election below — if the Overseer node dies, a new one is elected automatically and resumes from the queue. Since Solr 9.7, a node’s willingness to hold that role is controlled with -Dsolr.node.roles at startup:

# overseer:preferred steers overseer elections toward nodes not also serving as data
# nodes for heavy collections; data:off means this node hosts no shards/replicas at all.
bin/solr start -c -z zk1:2181,zk2:2181,zk3:2181/solr \
  -Dsolr.node.roles=data:off,overseer:preferred

# Default when unset: every node is a normal, overseer-eligible data node.
bin/solr start -c -z zk1:2181,zk2:2181,zk3:2181/solr -Dsolr.node.roles=data:on,overseer:allowed
# https://solr.apache.org/guide/solr/latest/deployment-guide/node-roles.html
# Which node is currently the Overseer, and what's still queued for it.
curl "http://localhost:8983/solr/admin/collections?action=OVERSEERSTATUS&wt=json"

Shard-leader election

Within one shard, exactly one replica is the leader at any moment; it is the one Solr routes writes to first, and it forwards each update to every other replica of that shard before acknowledging it. Election runs per shard, independently of the cluster-wide Overseer election, using the same ZooKeeper primitive: replicas race to create an ephemeral sequential znode under that shard’s election path, and the lowest-numbered live entry wins. In practice this means the first replica to come up for a brand-new shard wins by default, and afterwards a new election runs — among whichever replicas are both live and eligible — the moment the current leader’s node session expires (crash, network partition, restart).

A 3-node ZooKeeper ensemble sits above 3 Solr nodes; node 1 is the Overseer and hosts the NRT leader of shard 1 plus a PULL replica of shard 2; node 2 hosts a TLOG replica of shard 1 and the NRT leader of shard 2; node 3 hosts an NRT replica of shard 1 and a TLOG replica of shard 2 — gold dashed arrows from the ensemble to every node represent the state-sync and leader-election watches each node keeps against ZooKeeper

Only NRT and TLOG replicas are ever eligible; a PULL replica can never win, for the reason covered next.

# Full cluster state: every shard's replicas, their types, and which one is the leader.
curl "http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS&collection=books&wt=json"
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-shards-indexing.html

Losing a leader is invisible to a well-behaved client: a write sent to the old leader’s URL fails over, the CloudSolrClient (or a load balancer reading /live_nodes and state.json) discovers the new leader from the refreshed cluster state, and retries against it. Distributed indexing & search covers how a write actually gets routed to the correct shard’s leader in the first place, and how a scatter-gather query reaches every shard once leaders and replicas are settled.

Replica types: NRT, TLOG, PULL

Every replica added to a shard is created as one of three types, set once at ADDREPLICA/create time via type=:

Type What it does Leader-eligible?

NRT (default)

Indexes documents locally like a near-real-time core: maintains its own transaction log and writes straight into its own Lucene index.

Yes

TLOG

Maintains a transaction log like an NRT replica, but does not index locally — it keeps its Lucene index current by replicating it from the leader instead of applying each update itself.

Yes — replays its own transaction log to catch up before serving as leader

PULL

Neither logs nor indexes locally; it only pulls a completed index from the leader on a schedule, the same mechanism user-managed replication uses.

Never

The trade-off is indexing cost versus read capacity: an NRT replica pays the full cost of applying every update itself (so it can also lead), a TLOG replica pays only for the transaction log and gets its index for free from the leader (cheaper per-replica indexing load, still election-eligible as a durability fallback), and a PULL replica pays nothing at index time at all — pure read scale-out that can never take over writes. Solr’s own guidance is to keep a shard’s replicas uniform — all NRT, all TLOG, or TLOG leaders backed by PULL read replicas — and to avoid mixing all three types on one shard, since PULL’s lag then interacts unpredictably with which of the other two is currently leading.

# All-NRT: every replica indexes and any of them can lead -- the default, right for
# small-to-medium collections that want simple near-real-time visibility everywhere.
bin/solr create -c books -s 2 -rf 3

# TLOG + PULL: one indexing tier (implicit TLOG leader/replicas) plus pure read
# replicas added afterwards for search fan-out that never touches indexing load.
curl "http://localhost:8983/solr/admin/collections?action=ADDREPLICA&collection=books&shard=shard1&type=PULL&wt=json"
# https://solr.apache.org/guide/solr/latest/deployment-guide/solrcloud-shards-indexing.html

Reach for TLOG+PULL once indexing throughput and query fan-out start competing for the same nodes' CPU; until then, all-NRT is the simplest correct default. Indexing internals & performance covers the segment-merge and commit costs each replica type is actually paying for.

Bringing a cluster up

Putting the pieces together, standing up a new SolrCloud collection from nothing is: start every node against the ensemble, upload the configset once, then create the collection.

# On each of 3 hosts, start Solr in cloud mode against the same ensemble.
bin/solr start -c -z zk1:2181,zk2:2181,zk3:2181/solr -p 8983

# From any one host, load the configset the collection will reference (once).
bin/solr zk upconfig -z zk1:2181,zk2:2181,zk3:2181/solr -n books -d server/solr/configsets/_default/conf

# Create a 2-shard, 3-replica-per-shard collection using that configset.
bin/solr create -c books -s 2 -rf 3 -n books

# Confirm the shards, replicas, and elected leaders SolrCloud actually settled on.
curl "http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS&collection=books&wt=json"

From here, Collections API, configsets & replica placement covers reconfiguring and reloading a live collection’s configset, adding shards and replicas after creation, and shard splitting; Distributed indexing & search covers how document routing picks a shard and how a query fans out across the topology this page just built.