Saumitra's blog

Search / Analytics / Distributed Systems / Machine Learning / DSLs

Setting Up Solr Healthcheck Alert Using Zookeeper Watches

In this post we will see how to get instant health alerts if any replica of any collection becomes unhealthy in a cluster.

For this puporse, we will use zookeeper watches. But before we go there, lets see how solr maintains state information for a collection.

State management in solr

All state related information of solr collections is stored in zookeeper and shared by all nodes in the cluster. For each collection, solr maintains a state.json file.

You can see zookeeper data created by solr from command line:

1
2
3
4
5
[zk: localhost:2181(CONNECTED) 5] ls /
[configs, zookeeper, overseer, aliases.json, live_nodes, collections, overseer_elect, clusterstate.json]

[zk: localhost:2181(CONNECTED) 6] ls /collections
[]

Here’s collections is a zNode and for each newly created collection, a new child will be created under this node. Lets try creating a collection:

1
http://localhost:8983/solr/admin/collections?action=create&name=collection1&collection.configName=collection1&numShards=1

Here’s what zk data looks like after successful collection creation:

1
2
3
4
5
[zk: localhost:2181(CONNECTED) 7] ls /collections
[collection1]

[zk: localhost:2181(CONNECTED) 9] ls /collections/collection1
[leaders, state.json, leader_elect]

You will see that it created a a child zkNode named collection1 and under that it created a state.json file. Lets see content of that file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[zk: localhost:2181(CONNECTED) 11] get /collections/collection1/state.json
{"collection1":{
    "replicationFactor":"1",
    "router":{"name":"compositeId"},
    "maxShardsPerNode":"1",
    "autoAddReplicas":"false",
    "shards":{"shard1":{
        "range":"80000000-7fffffff",
        "state":"active",
        "replicas":{"core_node1":{
            "core":"collection1_shard1_replica1",
            "base_url":"http://127.0.1.1:8983/solr",
            "node_name":"127.0.1.1:8983_solr",
            "state":"active",
            "leader":"true"}}}}}}

Notice the state key on line 14. It maintains state information for each replica. So by checking this value we can know the health of each replica. Try create another collection with multiple shards and replicas and observer the content of state.json

Solr also exposes this information through CLUSTERSTATUS API mentioned below, but there are some rare instances where this API fails to gather and return data from zk. So we can’t rely on that:

1
http://localhost:8983/solr/admin/collections?action=clusterstatus

Zookeeper chroot used by solr is also exposed in solr admin UI. Navigate to cloud -> tree to access it and then expand the collections zNode. Here’s what it will look like:

Whenever a replica’s state changes, it will be reflected here. What we want is that whenever any replica’s state is non-active, we want to send an alert. Basically we need to watch each state.json for any change and then trigger an alert, if its not active.

To watch for changes, we will use zookeeper watches.

Zookeeper watches and curator

Zk watches allow clients to get notified when a znode changes in some way. Watches are set by operations, and are triggered by ZooKeeper when anything gets changed. For example, a watch can be placed on a znode which will be triggered when the znode data changes or the znode itself gets deleted.

Watch management is a low level task, so we will use a library called curator(https://curator.apache.org/) which abstract out lot of task like connection management, etc.

We will use PathChildrenCache and TreeCache recipies provided by curator for setting up watches:

PathChildrenCacheListener

It will be used to keep track of collection being created and deleted. Whenever a child is added, updated or removed, the Path Cache will change its state to contain the current set of children, the children’s data and the children’s state.

To watch for list of collection, we can define a listener on zk’s /collections zNode like below:

1
2
3
4
5
6
7
8
9
private val collectionCache = new PathChildrenCache(curatorClient, collectionsZNode, true)

collectionCache.getListenable.addListener(new PathChildrenCacheListener {
  override def childEvent(client: CuratorFramework, event: PathChildrenCacheEvent): Unit = {
    Logger.info(s"Change in path ${event.getData.getPath}")
  }
})

collectionCache.start(StartMode.BUILD_INITIAL_CACHE)

Line 9 will initialize the collectionCache with list of existing colletions and PathChildrenCacheListener will make sure that its always in sync with list of collection in zookeeper

TreeCache

Next we want to set watch on state.json of each collection. Instead of setting a watch individually on each collection’s state.json zNode, we will use TreeCache recipie which will watch all the files inside the /collections zNode. TreeCache attempts to keep all data from all children of a ZK path locally cached. This class will watch the ZK path, respond to update/create/delete events, pull down the data, etc. We can register a listener that will get notified when changes occur.

Since we are only intreseted in zNodes named state.json, we can ignore changes to other children. Here’s how to set the watch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private val clusterStateCache = new TreeCache(curatorClient, collectionsZNode)

clusterStateCache.getListenable.addListener(new TreeCacheListener {
  override def childEvent(client: CuratorFramework, event: TreeCacheEvent) = {
    try {
      if(event.getData != null) {
        val path = event.getData.getPath
        if (path.endsWith(".*state.json")) {
          Logger.info(s"Change in path $path")
          val data = new String(event.getData.getData)
          processState(data)
        }
      }
    } catch {
      case ex: Exception =>
        ex.printStackTrace()
        Logger.error(s"Exception in watcher child event. " + ex.getMessage)
    }
  }
})

clusterStateCache.start()

Here data in line 30 is same json which we saw at the starting. processState will parse this json and filter out unhealthy replicas, which will be send over through an email as an alert.

Note that a non-active state not necessarily means that a replica is unhealthy and needs manual intervention, so there might be few false alerts too. For instance, when a replica is under recovery due to load on a certain node, its state will show as recovery but it will auto recover after few minutes. To reduce noise from such alerts, instead of sending email immediately, an alert for a replica can be put on hold and rechecked after few minutes whether it auto-recovered on not

You can find full code for this utility at https://bitbucket.org/saumitra/solr-health-alerts