「RubyWorld Conference 2019」に協賛させていただきます

「RubyWorld Conference 2019」に、Goldスポンサーとして協賛させていただきます。
<期間>
2019年11月7日(木)、8日(金)
<会場>
島根県立産業交流会館「くにびきメッセ」
<主催>
RubyWorld Conference開催実行委員会
https://2019.rubyworld-conf.org/
「RubyWorld Conference 2019」に、Goldスポンサーとして協賛させていただきます。
<期間>
2019年11月7日(木)、8日(金)
<会場>
島根県立産業交流会館「くにびきメッセ」
<主催>
RubyWorld Conference開催実行委員会
https://2019.rubyworld-conf.org/
前回はドキュメントルーターとして”compositeId”を選んだときの挙動を説明しました。今回取り上げるのはもう一つのドキュメントルーター”implicit”です。
$ cd server/solr/configsets $ cp -r _default shard_test2 $ ../../scripts/cloud-scripts/zkcli.sh -zkhost localhost:9983 -cmd upconfig -confdir shard_test2/conf -confname shard_test2 $ ../../scripts/cloud-scripts/zkcli.sh -zkhost localhost:9983 -cmd upconfig -confdir shard_test2/conf -confname shard_test2 $ curl 'http://localhost:8983/solr/admin/collections?action=CREATE&router.name=implicit&name=shard_test2&shards=shard1,shard2,shard3,shard4&maxShardsPerNode=8&replicationFactor=1&collection.configName=shard_test2&wt=xml'
compositeIdのときはnumShardsでシャードの数を指定しますが、implicitではshardsパラメータで各シャードの名前を1個ずつ指定します。
前回と同じデータを、シャードの指定なしで投入してみます。
$ curl 'http://localhost:8983/solr/shard_test2/update?commit=true&indent=true' --data-binary @data.json -H 'Content-Type: application/json'
どういうシャード分けされたかを確認。
$ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test2/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done 0 9236 0 0
分散されずに特定のシャードにすべてのデータが投入されていました。
一旦削除して作り直し。
$ curl 'http://localhost:8983/solr/admin/collections?action=DELETE&name=shard_test2' $ curl 'http://localhost:8983/solr/admin/collections?action=CREATE&router.name=implicit&name=shard_test2&shards=shard1,shard2,shard3,shard4&maxShardsPerNode=8&replicationFactor=1&collection.configName=shard_test2&router.field=area&wt=xml'
投入時にシャードを指定するには、_route_パラメータを利用します。
$ cat d.json [ {"id":"1","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"} ] $ curl 'http://localhost:8983/solr/shard_test2/update?commit=true&indent=true&_route_=shard1' --data-binary @d.json -H 'Content-Type: application/json'
d.json は1件だけのデータです。指定したshard1に入ったことを確認します。
$ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test2/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done 1 0 0 0
shard2に1件追加
$ cat d.json [ {"id":"2","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"} ] $ curl 'http://localhost:8983/solr/shard_test2/update?commit=true&indent=true&_route_=shard2' --data-binary @d.json -H 'Content-Type: application/json' $ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test2/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done 1 1 0 0
shard3に1件追加
$ cat d.json [ {"id":"3","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"} ] $ curl 'http://localhost:8983/solr/shard_test2/update?commit=true&indent=true&_route_=shard3' --data-binary @d.json -H 'Content-Type: application/json' $ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test2/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done 1 1 1 0
shard4に1件追加
$ cat d.json [ {"id":"4","type":"官公庁","area":"住之江区","name":"軽自動車検査協会大阪主管事務所","address":"住之江区南港東3-4-62"} ] $ curl 'http://localhost:8983/solr/shard_test2/update?commit=true&indent=true&_route_=shard4' --data-binary @d.json -H 'Content-Type: application/json' $ for i in {1..4}; do curl -s "http://localhost:8983/solr/shard_test2/select?q=*:*&rows=0&shards=shard${i}"|jq '.response.numFound'; done 1 1 1 1
特に指定しなければ、全シャードを対象にした検索になります。
$ curl -s 'http://localhost:8983/solr/shard_test2/select?q=*:*'{ "responseHeader":{ "zkConnected":true, "status":0, "QTime":15, "params":{ "q":"*:*"}}, "response":{"numFound":4,"start":0,"maxScore":1.0,"docs":[ { "id":"1", "type":["官公庁"], "area":["住之江区"], "name":["軽自動車検査協会大阪主管事務所"], "address":["住之江区南港東3-4-62"], "_version_":1634782971384823808}, { "id":"2", "type":["官公庁"], "area":["住之江区"], "name":["軽自動車検査協会大阪主管事務所"], "address":["住之江区南港東3-4-62"], "_version_":1634782988669550592}, { "id":"3", "type":["官公庁"], "area":["住之江区"], "name":["軽自動車検査協会大阪主管事務所"], "address":["住之江区南港東3-4-62"], "_version_":1634782999227662336}, { "id":"4", "type":["官公庁"], "area":["住之江区"], "name":["軽自動車検査協会大阪主管事務所"], "address":["住之江区南港東3-4-62"], "_version_":1634783009317060608}] }}
シャードを指定しての検索。
$ curl -s 'http://localhost:8983/solr/shard_test2/select?q=*:*&shards=shard4' { "responseHeader":{ "zkConnected":true, "status":0, "QTime":4, "params":{ "q":"*:*", "shards":"shard4"}}, "response":{"numFound":1,"start":0,"maxScore":1.0,"docs":[ { "id":"4", "type":["官公庁"], "area":["住之江区"], "name":["軽自動車検査協会大阪主管事務所"], "address":["住之江区南港東3-4-62"], "_version_":1634783009317060608}] }}
ここまで見てきたように、compositeIdルーターは自動で程よく分散検索を実現させてくれるルーター、implicitルーターは自分で手動で制御したいときに向いたルーターです。今回使ったデータはcompositeIdルーターに向いたデータと言えるでしょう。
implicitルーターに向いているのは、たとえばログデータです。月単位でシャードを分けてシャードを指定しつつデータを投入、新しい月が来たらシャードを追加、といった使い方ができます。
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つのどちらかを選ぶことができます。
この記事では 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/" ]
大量の文書をインデックスするにあたってシャーディングの利用は不可欠です。想定される検索シーンのパターンに応じてドキュメントルーティングすることによってリソースを効率よく利用することができます。
こんにちわ。
リエです。
暑い日が続いておりますが、いかがお過ごしでしょうか。
いよいよ夏本番というところですが、わたしは紫外線がとても怖い。
紫外線対策は通年を通してやっていますが、夏は他の季節よりも気合を入れて対策をしないとすぐに日焼けをしてしまいます。(当たり前ですが)
使っていた日傘が古くなってきたので、いいのないかな〜と探していたところ、ずっと欲しかった日傘を手に入れることができました。
その名も【SUN BARRIER100】
https://uv100.jp/
SUN BARRIER100とは?
紫外線・赤外線・可視光線など、地上に届くすべての光を100%カットできる完全遮断の機能が備わっている日傘。(※日傘以外の商品もあり)
独自開発した3層構造の生地を使用しているので、効果は生地が破れない限り半永久。傘の色によって遮断率が変わらないことも特徴の一つ(一般的には黒い色ほど光を遮断すると言われています)
引用元:https://uv100.jp/secret.html
ひとつひとつ手縫いで作っているため大量生産ができず、大人気なこともあり入荷してもすぐに売り切れ。わたしも入荷待ちをしてやっと買えました。
ちなみに購入したサイズはSサイズです。
サイズはSサイズの他に折りたたみ(2段or3段)やLLサイズまであり、幅広いサイズがあるのも嬉しいポイントです。
SUN BARRIER100の前にも完全遮断100%の日傘を使っていましたが、SUN BARRIER100は軽い!そして使っていた日傘も完全遮断100%なのに涼しさが違う!というのが率直な感想です。Sサイズは小さいかなと思っていましたが、日常生活に使うにはちょうどいいサイズでした。(日傘は大きいと結構邪魔になるので、、)これから大事に長く使いたいと思います。
日傘が使えないシーンもあるので、帽子もほしいな〜と思っていたら、運よく帽子も入荷しており一緒に購入しました。
あっ、SUN BARRIER100は日傘の他にも帽子やサンバイザーなどあるんです。しかも日傘と同じ紫外線・赤外線・可視光線など、地上に届くすべての光を100%カットしてくれるのです。(回し者ではありません)
機能重視でかぶってみたら微妙なのかな〜と思っていましたが、届いたのを見てかわい〜!となりました。普段着ている服にも似合いそうでホッと一安心。
さっそく外出時に使ったのですが、つばが広くて肩までカバーしてくれてびっくりしました。あとかぶっていると涼しい!
海やレジャーなどで大活躍してくれそうです。
この最強アイテムたちと共に今年の夏を乗り切りたいと思います。
Solrを使った検索で、特定のフィールドのすべての値を取得したい場合がありました。SQLで言うところのdistnctです。これを実現する方法を調べてみました。
Solrの得意技であるFacetを使えば特定のフィールドのユニークな値をすべて列挙できます。
弊社から出しているSportareというアプリでは、スポーツ施設の検索にSolrを利用しています。各施設が持っている、実施できるスポーツ名のユニーク値を取得してみます。
$ curl 'http://localhost:8983/solr/sportare/select?facet.field=sports&facet.limit=10&facet.offset=0&facet=on&q=*:*&rows=0' { (略) "facet_counts":{ "facet_queries":{}, "facet_fields":{ "sports":[ "野球",4644, "バレーボール",4420, "バドミントン",4365, "バスケットボール",4218, "フィットネス",4089, "卓球",3918, "テニス",3493, "ソフトボール",3438, "サッカー",2711, "水泳",2687]}, "facet_ranges":{}, "facet_intervals":{}, "facet_heatmaps":{}}}
sportsフィールドの値でFacetを作っています。facet.limitとfacet.offsetを指定して最初の10件を取得しています。facet.limit=-1と指定すれば全件取れます。
StatsComponentは、インデックスに含まれるドキュメントの統計情報を扱うコンポーネントです。このコンポーネントにdistinctValuesというパラメータを含めたリクエストを送ることでフィールドのユニーク値を取得できます。
$ curl 'http://localhost:8983/solr/sportare-spots/select?q=*:*&rows=0&stats.field={!countDistinct=true distinctValues=true}sports&stats=true' { (略) "stats":{ "stats_fields":{ "sports":{ "distinctValues":["", "BMX", "アイスホッケー", "アメリカンフットボール", "アルペンスキー", "アーチェリー", "インディアカ", "インラインスケート", "ウィンドサーフィン", "エアロビクス", "オートレース", "カヌー", "カヤック", "カートレース", "カーリング", "カーレース", "キックボクシング", (略) "自転車競技", "野球", "陸上", "陸上競技", "馬術"], "countDistinct":89}}}}
Solrでdistinctを実現する方法を2つ挙げてみましたが、
などの利点がFacetにあるため、よほどの理由が無いかぎりはFacetを使うことになるだろうと思います。