はじめに
SolrCloudの機能の1つにシャーディングがあります。 たとえば4台のノードが利用できるときに、インデックスを4つのシャードに分けることにより、1ノードごとの検索負荷を小さくし、検索速度を向上させることができます。
シャーディングの設定
コレクション作成時に指定することでインデックスをいくつかのシャードに分けることができます。
まずサンプルの _default をコピーして configsets を用意します。
$ cd server/solr/configsets
$ cp -r _default shard_test
$ ../../scripts/cloud-scripts/zkcli.sh -zkhost localhost:9983 -cmd upconfig -confdir shard_test/conf -confname shard_test
4個のシャードからなるコレクションを作成
$ curl 'http://localhost:8983/solr/admin/collections?action=CREATE&router.name=compositeId&name=shard_test&numShards=4&maxShardsPerNode=8&replicationFactor=1&collection.configName=shard_test&wt=xml'
サンプルデータ
サンプルとして大阪の施設情報 を利用します。
$ grep -v ^# osaka_shisetsu20140106.txt |head -3
158 34.6164938333333 135.438210722222 http://lodosaka.hozo.jp/class/施設情報 官公庁 国の機関 住之江区 軽自動車検査協会大阪主管事務所 軽自動車検査協会大阪主管事務所 住之江区南港東3-4-62
157 34.6190439722222 135.442191833333 http://lodosaka.hozo.jp/class/施設情報 官公庁 国の機関 住之江区 大阪陸運支局なにわ自動車検査登録事務所 大阪陸運支局なにわ自動車検査登録事務所 住之江区南港東3-1-14
381 34.6109641111111 135.491388722222 http://lodosaka.hozo.jp/class/施設情報 官公庁 国の機関 住吉区 住吉税務署 住吉税務署 住吉区住吉2丁目17番37号 http://www.nta.go.jp/osaka/guide/zeimusho/osaka/sumiyoshi/index.htm
スクリプトを通してJSONに変換します。
{"id":"158","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"},
{"id":"157","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14"},
{"id":"381","type":"官公庁","area":"住吉区","name":"住吉税務署","address":"住吉区住吉2丁目17番37号"},
:
データを投入
$ curl 'http://localhost:8983/solr/shard_test/update?commit=true&indent=true' --data-binary @data.json -H 'Content-Type: application/json'
データ件数を確認
$ curl -s 'http://localhost:8983/solr/shard_test/select?q=*:*&rows=0'|jq '.response'
{
"numFound": 9236,
"start": 0,
"maxScore": 1,
"docs": []
}
特に何も指定しなければ、ドキュメントID(この場合はidフィールド)のハッシュ値に基づいてシャードが割り振られます。シャード毎に何件ずつ入っているかを確認します。
$ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done
2379
2360
2207
2290
4つのシャードにだいたい均等に分かれていますね。
ドキュメントルーティング
負荷分散さえできれば良い場合なら上記のようなシャード分けで十分訳に立ちますが、 検索パターンによっては特定のフィールド値を持つレコードが同じシャードに配置される構造になっていると都合が良いことがあります。
たとえばCMSの検索のような場合は、ユーザを指定して検索することが多くなります。この場合、同じユーザのインデックスが同じシャードに配置されるようになっていれば、検索時に特定のシャードにだけクエリを投げれば良く、分散検索に関するオーバーヘッドを抑えることができます
これを実現するのがドキュメントルーティングです。
2つのドキュメントルーター
何らかの値に基づいてレコードを各シャードに割り振るのがドキュメントルーターの役割です。ドキュメントルーターとして以下の2つのどちらかを選ぶことができます。
compositeId (デフォルト)ドキュメントIDのフィールド値に基づいて自動的にルーティングする。!区切りで任意の値をルーティング用のキーとして含めることができる implicitレコード毎にどのシャードにインデックスするかを手動で指定する
この記事では compositeId の動作を見て行きます。
compositeId
コレクション作成時にパラメータ router.name を指定しない場合にはデフォルトである compositeId ルータが使われます。(もちろん明示的に router.name=compositeId を指定しても良い)
上の例でも見たように、特に指定しなければドキュメントIDのハッシュ値に基づいてシャードが振り分けられます。何らかの値に基づいてシャードを振り分けたい場合には、その値を!区切りでドキュメントIDに含めます。
(例)
元のドキュメントID: 「1234」
区名に基づいてシャードを分けたいとき: 「中央区!1234」
このルールを使って投入用のJSONデータを少し変更します。
{"id":"住之江区!158","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"},
{"id":"住之江区!157","type":"官公庁","area":"住之江区","name":"大阪陸運支局なにわ自動車検査登録事務所","address":"住之江区南港東3-1-14"},
{"id":"住吉区!381","type":"官公庁","area":"住吉区","name":"住吉税務署","address":"住吉区住吉2丁目17番37号"},
:
先程投入したデータを全部削除してから新しいデータを投入します。
$ curl -s 'http://localhost:8983/solr/shard_test/update?stream.body=*:* &commit=true'
$ curl -s 'http://localhost:8983/solr/shard_test/update?commit=true&indent=true' --data-binary @data2.json -H 'Content-Type: application/json'
シャードごとに、どの区のデータが何件含まれているかを出力してみます。特定のシャードには特定の区だけが存在していることが分かります。
$ curl -s 'http://localhost:8983/solr/shard_test/select?facet.field=area_str&facet=on&q=*:*&rows=0&shards=shard1'|jq .facet_counts.facet_fields.area_str
[
"中央区",
509,
"鶴見区",
310,
"福島区",
270
]
$ curl -s 'http://localhost:8983/solr/shard_test/select?facet.field=area_str&facet=on&q=*:*&rows=0&shards=shard2'|jq .facet_counts.facet_fields.area_str
[
"北区",
566,
"住之江区",
402,
"東住吉区",
402,
"天王寺区",
349,
"都島区",
319,
"大正区",
318,
"東成区",
318
]
$ curl -s 'http://localhost:8983/solr/shard_test/select?facet.field=area_str&facet=on&q=*:*&rows=0&shards=shard3'|jq .facet_counts.facet_fields.area_str
[
"淀川区",
467,
"住吉区",
409,
"生野区",
401,
"西淀川区",
360,
"西区",
357,
"此花区",
349,
"旭区",
274,
"浪速区",
236
]
$ curl -s 'http://localhost:8983/solr/shard_test/select?facet.field=area_str&facet=on&q=*:*&rows=0&shards=shard4'|jq .facet_counts.facet_fields.area_str
[
"平野区",
610,
"東淀川区",
469,
"城東区",
449,
"西成区",
388,
"港区",
322,
"阿倍野区",
308,
"大阪市以外",
76
]
ドキュメントIDのハッシュ値に基づいて分割した場合にはほぼ均等に割り当てられていましたが、特定のフィールド値に基づいて分割する場合には、そのフィールド値の偏りが分割の偏りとなってしまうことに注意が必要です。
$ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done
1089
2674
2853
2622
検索時のドキュメントルーティング
シャードに関するパラメータが無い場合には、すべてのシャードにクエリが投げられます。
$ curl -s 'http://localhost:8983/solr/shard_test/select?debugQuery=on&rows=0&q='`echo "area_str:中央区 AND name:消防署"|jq -s -R -r @uri` |jq '.debug.track.EXECUTE_QUERY|keys'
[
"http://localhost:8983/solr/shard_test_shard1_replica_n1/",
"http://localhost:8983/solr/shard_test_shard2_replica_n2/",
"http://localhost:8983/solr/shard_test_shard3_replica_n4/",
"http://localhost:8983/solr/shard_test_shard4_replica_n6/"
]
「中央区」が存在するシャードにだけクエリを投げられれば、分散検索に関するオーバーヘッドを抑えることができます。たとえばshards=shard1という風にshardsパラメータでシャードを直接指定できます。
$ curl -s 'http://localhost:8983/solr/shard_test/select?shards=shard1&debugQuery=on&rows=0&q='`echo "area_str:中央区 AND name:消防署"|jq -s -R -r @uri` |jq '.debug.track.EXECUTE_QUERY|keys'
[
"http://localhost:8983/solr/shard_test_shard1_replica_n1/"
]
ただし、検索対象がどのシャードに属するのかがクエリ作成時には分からない場合がほとんどです。分割の基準となるフィールド値を_route_ パラメータで指定することでシャードを指定することができます。たとえば、中央区だけを対象としたい場合には _route_=中央区 を指定します。
$ curl -s 'http://localhost:8983/solr/shard_test/select?debugQuery=on&rows=0&q='`echo "area_str:中央区 AND name:消防署"|jq -s -R -r @uri`'&_route_='`echo 中央区\!|jq -s -R -r @uri` |jq '.debug.track.EXECUTE_QUERY|keys'
[
"http://localhost:8983/solr/shard_test_shard1_replica_n1/"
]
おわりに
大量の文書をインデックスするにあたってシャーディングの利用は不可欠です。想定される検索シーンのパターンに応じてドキュメントルーティングすることによってリソースを効率よく利用することができます。