Solr Cloud を Vagrant で構築した



いよいよ Solr Cloud を構築します．
Solr Cloud を構築するには，Solr はもちろん ZooKeeper も必要です．
これまで Vagrant で [Solr を Standalone で構築](https://qiita.com/maitake9116/items/7cd77b4cd4fefbe4915e)したり，[ZooKeeper を Ensemble で構築](https://qiita.com/maitake9116/items/933cba14cc76235b3c4f)してきたのもこのためでした．

## Solr Cloud

イメージはこんな感じです．

- ZooKeeper : 3
- Solr node: 4
- shard : 2
- replica : 2

Zookeeper 3 個，Solr node 4 個の計 7 個の VM を構築します．

![solr_cloud_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/183593ad-4be9-1629-bb37-fbbc263df279.png)


## 構成

構成を以下に示します．

```text
D:\vagrant\solrcloud
┣Vagrantfile
┣create_zkensemble.sh
┗create_solrnode.sh
```

### Vagrantfile

Zookeeper 3 個，Solr node 4 個の計 7 個の VM を構築します．
Solr を Cloud モードで起動する際に ZooKeeper の接続先を指定する必要があるので Vagrantfile で接続先文字列を用意するのがポイントです．


```ruby
# ZooKeeper 数
zk_cnt = 3
# Solr node 数
node_cnt = 4

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"

  #--- proxy 設定 ---#
  no_proxy_address = "localhost,127.0.0.1"
  (1..zk_cnt).each do |i|
    no_proxy_address += ",zk#{i},192.168.33.1#{i}"
  end
  (1..node_cnt).each do |i|
    no_proxy_address += ",node#{i},192.168.33.2#{i}"
  end
  config.proxy.enabled  = true
  config.proxy.http     = "http://user_id:password@proxy_address:proxy_port"
  config.proxy.https    = "http://user_id:password@proxy_address:proxy_port"
  config.proxy.no_proxy = no_proxy_address
  #--- /proxy 設定 ---#
  
  #--- ZooKeeper アンサンブル構築 ---#
  (1..zk_cnt).each do |i|
    config.vm.define "zk#{i}" do | zk |
      zk.vm.hostname = "zk#{i}"
      zk.vm.network "private_network", ip: "192.168.33.1#{i}"
      zk.vm.provision :hosts, :sync_hosts => true
      zk.vm.provision :shell, path: "./create_zkensemble.sh", args: "#{i} #{zk_cnt}"
    end
  end
  #--- /ZooKeeper アンサンブル構築 ---#

  #--- ZooKeeper アンサンブルの接続先文字列 ---#
  zkensemble = ""
  (1..zk_cnt).each do |i|
    zkensemble += "192.168.33.1#{i}:2181,"
  end
  zkensemble.slice!(/,$/)
  #--- /ZooKeeper アンサンブルの接続先文字列 ---#

  #--- Solr Cloud 構築 ---#
  (1..node_cnt).each do |i|
    config.vm.define "node#{i}" do | node |
      node.vm.provider :virtualbox do |vb|
          # Solr node だけメモリ増やしてます
          vb.customize ["modifyvm", :id, "--memory", "2048"]
          vb.customize ["modifyvm", :id, "--cpus", "2"]
      end
      node.vm.hostname = "node#{i}"
      node.vm.network "private_network", ip: "192.168.33.2#{i}"
      node.vm.provision :hosts, :sync_hosts => true
      node.vm.provision :shell, path: "./create_solrcloud.sh", args: "192.168.33.2#{i} #{zkensemble}"
    end
  end
  #--- /Solr Cloud 構築 ---#

end
```


### create_zkensemble.sh

ZooKeeper Ensemble を構築したときと同じです．

```shell
# Java をインストールする
yum install -y java-1.8.0-openjdk

# ZooKeeper を構築する
cd /var/tmp
yum install -y wget
wget http://ftp.jaist.ac.jp/pub/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
tar xvf zookeeper-3.4.14.tar.gz
cp -ar zookeeper-3.4.14 /opt/
ln -s /opt/zookeeper-3.4.14 /opt/zookeeper

# ZooKeeper 用のユーザを作成する
groupadd zookeeper
useradd -g zookeeper -d /opt/zookeeper -s /sbin/nologin zookeeper
chown -R zookeeper.zookeeper /opt/zookeeper/*

# ZooKeeperの設定ファイルを用意する
cd /opt/zookeeper/conf
cp zoo_sample.cfg zoo.cfg

# dataDir を tmp から var に変更する
mkdir /var/lib/zookeeper
chown zookeeper.zookeeper /var/lib/zookeeper
cd /opt/zookeeper/conf
sed -i -e 's#dataDir=.*$#dataDir=/var/lib/zookeeper#' zoo.cfg

# ZooKeeper アンサンブルの設定を追記する
# $1 : ZooKeeper のノードID
# $2 : ZooKeeper アンサンブルの構成ノード数
echo "# ZooKeeper Ensemble" >> /opt/zookeeper/conf/zoo.cfg
for i in `seq 1 $2`
do
  if [ $i = $1 ] ; then
    # ノードIDが自分のものと一致する場合は 0.0.0.0 とする．
    echo "server.${i}=0.0.0.0:2888:3888" >> /opt/zookeeper/conf/zoo.cfg
  else
    echo "server.${i}=192.168.33.1${i}:2888:3888" >> /opt/zookeeper/conf/zoo.cfg
  fi
done
# dataDir にノードIDが記載された myid ファイルを用意する必要がある
echo $1 >> /var/lib/zookeeper/myid

# ZooKeeper を起動する
cd /opt/zookeeper
bin/zkServer.sh start

# Port を開放する
systemctl enable firewalld.service
systemctl start firewalld.service
firewall-cmd --zone=public --add-port=2181/tcp --add-port=2888/tcp --add-port=3888/tcp --permanent
firewall-cmd --reload

# ZooKeeper の状態を確認する
cd /opt/zookeeper
bin/zkServer.sh status
yum install -y nc
echo ruok | nc localhost 2181

# 終了ステータスが 0 でない場合に Vagrant が異常終了とみなし VM が連続生成されないことがあるので明示する
exit 0
```


### create_solrcloud.sh

Solr を Cloud モードで起動します．

```shell
# Java をインストールする
yum install -y java-1.8.0-openjdk

# Solr を構築する
cd /usr/local/src/
yum install -y wget
wget http://ftp.jaist.ac.jp/pub/apache/lucene/solr/8.1.1/solr-8.1.1.tgz
tar xzf solr-8.1.1.tgz
./solr-8.1.1/bin/install_solr_service.sh solr-8.1.1.tgz

# Solr を Cloud モードで起動する
# $1 : Solr node の IPアドレス
# $2 : ZooKeeper アンサンブルの接続先文字列
sudo -u solr /opt/solr/bin/solr stop
sudo -u solr /opt/solr/bin/solr start -cloud -s /var/solr/data -p 8983 -z $2 -h $1

# Port を開放する
systemctl enable firewalld.service
systemctl start firewalld.service
firewall-cmd --zone=public --add-port=8983/tcp --permanent
firewall-cmd --reload

# 終了ステータスが 0 でない場合に Vagrant が異常終了とみなし VM が連続生成されないことがあるので明示する
exit 0
```


## Solr Cloud を構築する

```powershell
cd D:\vagrant\solrcloud
vagrant up
```

<!-- 
```powershell
# Solr を停止する
vagrant ssh node1 -c "sudo -u solr /opt/solr/bin/solr stop"
vagrant ssh node2 -c "sudo -u solr /opt/solr/bin/solr stop"
vagrant ssh node3 -c "sudo -u solr /opt/solr/bin/solr stop"
vagrant ssh node4 -c "sudo -u solr /opt/solr/bin/solr stop"
# Solr を Cloud モードで起動する
vagrant ssh node1 -c "sudo -u solr /opt/solr/bin/solr restart -cloud -s /var/solr/data -p 8983 -z 192.168.33.11:2181,192.168.33.12:2181,192.168.33.13:2181 -h 192.168.33.21"
vagrant ssh node2 -c "sudo -u solr /opt/solr/bin/solr restart -cloud -s /var/solr/data -p 8983 -z 192.168.33.11:2181,192.168.33.12:2181,192.168.33.13:2181 -h 192.168.33.22"
vagrant ssh node3 -c "sudo -u solr /opt/solr/bin/solr restart -cloud -s /var/solr/data -p 8983 -z 192.168.33.11:2181,192.168.33.12:2181,192.168.33.13:2181 -h 192.168.33.23"
vagrant ssh node4 -c "sudo -u solr /opt/solr/bin/solr restart -cloud -s /var/solr/data -p 8983 -z 192.168.33.11:2181,192.168.33.12:2181,192.168.33.13:2181 -h 192.168.33.24"
```
 -->


http://192.168.33.21:8983/solr/#/ にアクセスすると Cloud という項目が追加されていることがわかります．

![solr_dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/a2a1b506-8312-07bf-b5fb-11d544e24c66.png)


Cloud をクリックすると node が一覧に表示されます．
4個 node を作成したので4個表示されています．
まだ collection を作成していないので Collections，Replicas は空です．

![solr_cloud_nodes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/d2e10f84-66e6-2c8f-e82f-e9d88203308c.png)


ZK Status をクリックすると ZooKeeper が一覧に表示されます．
3個 ZooKeeper を作成したので3個でアンサンブルが構成されています．
今回は 192.168.33.12 が leader に選出されたようです．

![solr_cloud_zk_status.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/cda2aba9-5c2e-dc8a-55ae-301e53a45fd0.png)


Graph をクリックすると Solr Cloud の構成が可視化されます．
まだ collection を作成していないので何も表示されません．

![solr_cloud_graph_empty.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/b15a53c6-016b-61da-9424-5238e2511bd9.png)



## collection を作成する

早速 collection を作ります．

```powershell
vagrant ssh node1 -c "sudo -u solr /opt/solr/bin/solr create_collection -c examplecollection -p 8983 -shards 2 -replicationFactor 2"
```

Dashboard を見ると collection が追加されていることがわかります．



![solr_dashborad_collection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/78f9fb9d-d43f-84f9-f9c2-7395ce726ac4.png)


collection を選択すると shard 2 replication 2 で構成されていることがわかります．

![solr_collection_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/3075b597-a3bf-40c4-df65-5bf5813edf62.png)


Cloud の Graph を見ると collection の shard に各 node が割り当てられていることがわかります．

![solr_cloud_graph_collection_created.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246211/4d03d2f7-0502-87bb-09c0-76192ea1e4a0.png)



## インデクシング(文書登録)する

collection にインデクシングをおこないます．

```powershell
vagrant ssh node1 -c "sudo -u solr /opt/solr/bin/post -c examplecollection -p 8983 /opt/solr/example/exampledocs/*.xml"
```

検索してみます．

```cmd
$ curl -X GET "http://192.168.33.21:8983/solr/examplecollection/select?q=*:*&fl=id&sort=id%20asc&wt=json&indent=on" --noproxy 192.168.33.21                
{                                                                                                                                                          
  "responseHeader":{                                                                                                                                       
    "zkConnected":true,                                                                                                                                    
    "status":0,                                                                                                                                            
    "QTime":7,                                                                                                                                             
    "params":{                                                                                                                                             
      "q":"*:*",                                                                                                                                           
      "indent":"on",                                                                                                                                       
      "fl":"id",                                                                                                                                           
      "sort":"id asc",                                                                                                                                     
      "wt":"json"}},                                                                                                                                       
  "response":{"numFound":32,"start":0,"docs":[                                                                                                             
      {                                                                                                                                                    
        "id":"0579B002"},                                                                                                                                  
      {                                                                                                                                                    
        "id":"100-435805"},                                                                                                                                
      {                                                                                                                                                    
        "id":"3007WFP"},                                                                                                                                   
      {                                                                                                                                                    
        "id":"6H500F0"},                                                                                                                                   
      {                                                                                                                                                    
        "id":"9885A004"},                                                                                                                                  
      {                                                                                                                                                    
        "id":"EN7800GTX/2DHTV/256M"},                                                                                                                      
      {                                                                                                                                                    
        "id":"EUR"},                                                                                                                                       
      {                                                                                                                                                    
        "id":"F8V7067-APL-KIT"},                                                                                                                           
      {                                                                                                                                                    
        "id":"GB18030TEST"},                                                                                                                               
      {                                                                                                                                                    
        "id":"GBP"}]                                                                                                                                       
  }}                                                                                                                                                       
```

32件ヒットしました．
rows を指定していないので id は 10 件のみの表示となっています．
これで構築が完了しました．


## shard を指定した検索

まずは shard1 を指定して検索します．

```cmd
$ curl -X GET "http://192.168.33.21:8983/solr/examplecollection/select?q=*:*&fl=id&shards=shard1&sort=id%20asc&wt=json&indent=on" --noproxy 192.168.33.21    
{                                                                                                                                                            
  "responseHeader":{                                                                                                                                         
    "zkConnected":true,                                                                                                                                      
    "status":0,                                                                                                                                              
    "QTime":4,                                                                                                                                               
    "params":{                                                                                                                                               
      "q":"*:*",                                                                                                                                             
      "shards":"shard1",                                                                                                                                     
      "indent":"on",                                                                                                                                         
      "fl":"id",                                                                                                                                             
      "sort":"id asc",                                                                                                                                       
      "wt":"json"}},                                                                                                                                         
  "response":{"numFound":14,"start":0,"docs":[                                                                                                               
      {                                                                                                                                                      
        "id":"3007WFP"},                                                                                                                                     
      {                                                                                                                                                      
        "id":"EN7800GTX/2DHTV/256M"},                                                                                                                        
      {                                                                                                                                                      
        "id":"GB18030TEST"},                                                                                                                                 
      {                                                                                                                                                      
        "id":"GBP"},                                                                                                                                         
      {                                                                                                                                                      
        "id":"IW-02"},                                                                                                                                       
      {                                                                                                                                                      
        "id":"MA147LL/A"},                                                                                                                                   
      {                                                                                                                                                      
        "id":"TWINX2048-3200PRO"},                                                                                                                           
      {                                                                                                                                                      
        "id":"USD"},                                                                                                                                         
      {                                                                                                                                                      
        "id":"VDBDB1A16"},                                                                                                                                   
      {                                                                                                                                                      
        "id":"VS1GB400C3"}]                                                                                                                                  
  }}                                                                                                                                                         
```

14件ヒットしました．

次に shard2 を指定して検索します．

```cmd
$ curl -X GET "http://192.168.33.21:8983/solr/examplecollection/select?q=*:*&fl=id&shards=shard2&sort=id%20asc&wt=json&indent=on" --noproxy 192.168.33.21
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":0,
    "params":{
      "q":"*:*",
      "shards":"shard2",
      "indent":"on",
      "fl":"id",
      "sort":"id asc",
      "wt":"json"}},
  "response":{"numFound":18,"start":0,"docs":[
      {
        "id":"0579B002"},
      {
        "id":"100-435805"},
      {
        "id":"6H500F0"},
      {
        "id":"9885A004"},
      {
        "id":"EUR"},
      {
        "id":"F8V7067-APL-KIT"},
      {
        "id":"NOK"},
      {
        "id":"SOLR1000"},
      {
        "id":"SP2514N"},
      {
        "id":"UTF8TEST"}]
  }}
```

18件ヒットしました．

各 shard から異なる結果が得られたことから，文書を登録すると自動で各 shard に文書が登録されることがわかりました．

今度は shard1 と shard2 両方指定して検索します．

```cmd
$ curl -X GET "http://192.168.33.21:8983/solr/examplecollection/select?q=*:*&fl=id&shards=shard1,shard2&sort=id%20asc&wt=json&indent=on" --noproxy 192.168.33.21                                
{                                                                                                                                                                                               
  "responseHeader":{                                                                                                                                                                            
    "zkConnected":true,                                                                                                                                                                         
    "status":0,                                                                                                                                                                                 
    "QTime":17,                                                                                                                                                                                 
    "params":{                                                                                                                                                                                  
      "q":"*:*",                                                                                                                                                                                
      "shards":"shard1,shard2",                                                                                                                                                                 
      "indent":"on",                                                                                                                                                                            
      "fl":"id",                                                                                                                                                                                
      "sort":"id asc",                                                                                                                                                                          
      "wt":"json"}},                                                                                                                                                                            
  "response":{"numFound":32,"start":0,"docs":[                                                                                                                                                  
      {                                                                                                                                                                                         
        "id":"0579B002"},                                                                                                                                                                       
      {                                                                                                                                                                                         
        "id":"100-435805"},                                                                                                                                                                     
      {                                                                                                                                                                                         
        "id":"3007WFP"},                                                                                                                                                                        
      {                                                                                                                                                                                         
        "id":"6H500F0"},                                                                                                                                                                        
      {                                                                                                                                                                                         
        "id":"9885A004"},                                                                                                                                                                       
      {                                                                                                                                                                                         
        "id":"EN7800GTX/2DHTV/256M"},                                                                                                                                                           
      {                                                                                                                                                                                         
        "id":"EUR"},                                                                                                                                                                            
      {                                                                                                                                                                                         
        "id":"F8V7067-APL-KIT"},                                                                                                                                                                
      {                                                                                                                                                                                         
        "id":"GB18030TEST"},                                                                                                                                                                    
      {                                                                                                                                                                                         
        "id":"GBP"}]                                                                                                                                                                            
  }}                                                                                                                                                                                            
```

shard を指定しない場合と同じ結果になりました．
つまりクエリで shard を全て指定しなくても全ての shard から検索することがわかりました．



## まとめ

- collection を作成すると shard に各 node が自動で割り当てられます．
- Solr Cloud の collection に文書を登録すると自動で各 shard に文書が登録されます．
- Solr Cloud の collection を検索する際に shard を指定しなくても全 shard から検索されます．