Docker + SolrCloud
概要
以下の書籍の内容をベースにDocker + Solr8でSolrCloud環境を構築した。
構成
- ZooKeeper
- Solr
- Nginx
- 2つのSolrへリクエストを振り分けるLB
- 片方が死んでいる場合は生きている方に流す
コード
https://github.com/sota2502/japan_address
使用するデータ
郵便番号データダウンロード - 日本郵便 を使用した。 全都道府県で約12万件。 Shift-JIS → UTF-8 への変更とスキーマに合わせたヘッダーを追加している。
ZooKeeper
いきなりSolrCloudの構成を作る前にZooKeeperに慣れておくとよい。
docker-compose-zk.yml
version: '3.7' x-zoo-service: &zoo-service image: zookeeper:3.5 restart: always networks: - solr services: zoo1: <<: *zoo-service container_name: zoo1 hostname: zoo1 ports: - 2181:2181 env_file: ./zoo.env environment: ZOO_MY_ID: 1 volumes: - ./data/zoo1/data:/data - ./data/zoo1/datalog:/datalog zoo2: <<: *zoo-service container_name: zoo2 hostname: zoo2 ports: - 2182:2181 env_file: ./zoo.env environment: ZOO_MY_ID: 2 volumes: - ./data/zoo2/data:/data - ./data/zoo2/datalog:/datalog zoo3: <<: *zoo-service container_name: zoo3 hostname: zoo3 ports: - 2183:2181 env_file: ./zoo.env environment: ZOO_MY_ID: 3 volumes: - ./data/zoo3/data:/data - ./data/zoo3/datalog:/datalog networks: solr:
各々のZooKeeperのデータを永続化するため、/data
,/datalog
をVolumeにマウントする。これによりrestartなどをしてもデータは失われない。
ZooKeeperのクラスタを起動。
$ docker-compose -f docker-compose-zk.yml up -d
ZooKeeperのクライアントを用意する。Macであればhomebrewでインストールするのが簡単。
$ brew install zookeeper
zoo1
に接続してznodeを作成。
$ zkCli -server localhost:2181 create /hoge fuga Connecting to localhost:2181 WATCHER:: WatchedEvent state:SyncConnected type:None path:null Created /hoge
先ほど作ったznodeのデータを zoo2
から読み出す。
$ zkCli -server localhost:2182 get /hoge Connecting to localhost:2182 WATCHER:: WatchedEvent state:SyncConnected type:None path:null fuga
SolrのCollection用Configsetの作成
_default
のConfigsetをベースに郵便番号を管理するためのConfigsetを作成する。これはCollectionを作るときに使う。
以下のコマンドを実行して、テンポラリなコンテナを作って、その中から _default
Configsetsを取り出す。
$ docker create --name mysolr solr:8.5.2 $ docker cp mysolr:/opt/solr/server/solr/configsets/_default ./configsets $ docker rm mysolr
この中で solrconfig.xml
、 schema.xml
を編集していく。
特別なポイントはUUIDの設定をしているところ。 Solrのドキュメントを更新するためにはUniqueKeyが必要になる。しかし、郵便番号データにはUniqueKeyに適したものが存在しない。(同一の郵便番号の市町村が複数存在するケースがある) この制約はCSVを編集してIDを採番することでも満たすことは可能。 しかし、UUIDを採番するようにしておくほうが手間は省ける。
solrconfig.xml
差分は以下の通り。
38a39 > <schemaFactory class="ClassicIndexSchemaFactory" /> 1111c1112 < <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}" --- > <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:false}" 1117a1119,1124 > <updateRequestProcessorChain name="uuid" default="true"> > <processor class="solr.UUIDUpdateProcessorFactory"> > <str name="fieldName">uuid</str> > </processor> > <processor class="solr.RunUpdateProcessorFactory" /> > </updateRequestProcessorChain>
<schemaFactory class="ClassicIndexSchemaFactory" />
は managed-schema
の代わりに schema.xml
を使用する場合に記述する。
add-unknown-fields-to-the-schema
の UpdateRequestProcessorChain
のdefault値をfalseにしておく。これはschemalessモードを使わないための設定。
uuid
の UpdateRequestProcessorChain
はUUIDを採番するための設定。
schema.xml
_default
Configsetsには schema.xml
は存在しない。
これは managed-schema
を schema.xml
にリネームして手動編集する。
差分は以下のようになる。これは郵便番号データに対応したfieldの定義の追加と、不要なfieldの定義の削除、UniqueKeyの変更をしている。
111a112,127 > <field name="uuid" type="uuid" multiValued="false" indexed="true" required="true" stored="true"/> > <field name="jis" type="string" indexed="true" required="true" stored="true"/> > <field name="postcode" type="string" indexed="true" required="true" stored="true"/> > <field name="postcode5" type="string" indexed="true" required="true" stored="true"/> > <field name="prefecture_kana" type="text_ja" required="true"/> > <field name="city_kana" type="text_ja" required="true"/> > <field name="town_area_kana" type="text_ja" required="true"/> > <field name="prefecture" type="text_ja" indexed="true" required="true" stored="true"/> > <field name="city" type="text_ja" indexed="true" required="true" stored="true"/> > <field name="town_area" type="text_ja" indexed="true" required="true" stored="true"/> > <field name="is_one_town_by_multi_postcode" type="boolean" required="true"/> > <field name="is_need_small_area_address" type="boolean" required="true"/> > <field name="is_chome" type="boolean" required="true"/> > <field name="is_multi_town_by_one_postcode" type="boolean" required="true"/> > <field name="updated" type="boolean" required="true"/> > <field name="update_reason" type="boolean" required="true"/> 113d128 < <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 117,122d131 < <!-- If you don't use child/nested documents, then you should remove the next two fields: --> < <!-- for nested documents (minimal; points to root document) --> < <field name="_root_" type="string" indexed="true" stored="false" docValues="false" /> < <!-- for nested documents (relationship tracking) --> < <field name="_nest_path_" type="_nest_path_" /><fieldType name="_nest_path_" class="solr.NestPathField" /> < 169c178 < <uniqueKey>id</uniqueKey> --- > <uniqueKey>uuid</uniqueKey> 1012a1022,1023 > <fieldType name="uuid" class="solr.UUIDField" indexed="true" /> > 1023d1033 <
SolrCloud環境の構築
docker-compose
で ZooKeeper
、 Solr
、 Nginx
のSolrCloud環境を構築する。
docker-compose
docker-compose.yml
やその他に必要なファイルは以下のようになる。
docker-compose.yml
version: '3.7' x-zoo-service: &zoo-service image: zookeeper:3.5 restart: always networks: - solr x-solr-service: &solr-service image: solr:8.5.2 environment: ZK_HOST: zoo1:2181,zoo2:2181,zoo3:2181 SOLR_JAVA_MEM: -Xms1g -Xmx1g -Xmn512m networks: - solr depends_on: - zoo1 - zoo2 - zoo3 services: zoo1: <<: *zoo-service container_name: zoo1 hostname: zoo1 env_file: ./zoo.env environment: ZOO_MY_ID: 1 volumes: - ./data/zoo1/data:/data - ./data/zoo1/datalog:/datalog zoo2: <<: *zoo-service container_name: zoo2 hostname: zoo2 env_file: ./zoo.env environment: ZOO_MY_ID: 2 volumes: - ./data/zoo2/data:/data - ./data/zoo2/datalog:/datalog zoo3: <<: *zoo-service container_name: zoo3 hostname: zoo3 env_file: ./zoo.env environment: ZOO_MY_ID: 3 volumes: - ./data/zoo3/data:/data - ./data/zoo3/datalog:/datalog solr1: <<: *solr-service container_name: solr1 volumes: - ./data/solr1:/var/solr solr2: <<: *solr-service container_name: solr2 volumes: - ./data/solr2:/var/solr nginx: image: nginx:1.19.2 container_name: nginx volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: - 8080:80 networks: - solr depends_on: - solr1 - solr2 networks: solr:
Solrのデータを永続化するために /var/solr
をVolumeにマウントする。
zoo.env
ZOO_SERVERS=server.1=zoo1:2888:3888;2181 server.2=zoo2:2888:3888;2181 server.3=zoo3:2888:3888;2181 ZOO_4LW_COMMANDS_WHITELIST=mntr,conf,ruok
nginx.conf
user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { upstream solr { server solr1:8983; server solr2:8983; } server { listen 80; client_max_body_size 20M; location / { proxy_pass http://solr; } } }
コンテナを起動。
$ docker-compose up -d
起動が完了したら http://localhost:8080/solr/ にブラウザでアクセスする。SolrのWeb管理画面が表示される。 Cloud → Graph でSolrノードの繋がりが見える。
ConfigsetsのUpload
[SolrのCollection用Configsetの作成] で作ったConfigsetsをアップロードする。 これは一度zipで一つにしたものをAPIで送る。
$ cd configsets/conf $ zip -r japan_address.zip * $ curl -X POST --header "Content-Type:application/octet-stream" \ --data-binary @japan_address.zip \ "http://localhost:8080/solr/admin/configs?action=UPLOAD&name=japan_address"
ZooKeeperコンテナに対して2181ポートをマッピングしておいて、 zkCli
で ls
コマンドを実行するとConfigsetsに対するznodeができているのが確認できる。
(上記のdocker-compose.ymlではポートをマッピングしていないので、これを行う場合には ports
の記述を追加する必要がある)
$ zkCli -server localhost:2181 ls /configsets
Collectionの作成
http://localhost:8080/solr/#/~collections:Solr管理コンソール からCollectionを作成する。 今回はシャード1、レプリカ2なので以下のように入力する。
項目 | 値 |
---|---|
name | japan_address |
configset | japan_address |
numShards | 1 |
replicationFactor | 2 |
Add Collection
をクリックして画面をリロードするとCollectionが作成されたのが確認できる。
CSVデータのインポート
以下のコマンドを実行してCSVデータをインポートする。これは結構時間がかかる。
$ curl -H 'Content-type: text/csv; charset=utf-8' \ --data-binary @ken_all.csv \ 'http://localhost:8080/solr/japan_address/update?commit=true'
検索の実行
Curlコマンドか Solr管理画面 でクエリを実行すると結果が返ってくるのが見える。
$ curl 'http://localhost:8080/solr/japan_address/select?q=*:*'
フェールオーバーの確認
現在は2つのSolrにNginxがリクエストを振り分けている状態。 ここから片方のSolrが落ちた状態でもリクエストを処理していることを確認する。
以下のコマンドで solr2
のコンテナを停止させる。
$ docker-compose stop solr2
Solr管理画面のCloud → Tree で片方が停止しているのが把握できる。
Curlで何回かクエリを実行して、いずれもレスポンスが返ってきているのが確認できる。