block join toranomaki
TRANSCRIPT
Block-Join 虎の巻 デジタル・インフォメーション・テクノロジー株式会社
海老澤 志信
自己紹介
• 海老澤 志信
• 所属
• 現業務 リクルートグループの検索システム
コンサル・開発・保守・運用・サイト導入
• Solr歴 Luceneのリリースノートに日本語で名前が載った唯一の男
(2015/05/13現在)
※詳しくは前回のスライドにて……
http://www.slideshare.net/ebisawashinobu/heliosearch-vs-solr
本日のアジェンダ
(Solr4.10系を調べていた時)
・このschema.xmlのパラメータなんだろう
<field name="_root_" type="string" indexed="true" stored="false"/>
本日のアジェンダ
(Solr4.10系を調べていた時)
・このschema.xmlのパラメータなんだろう
↓
・Block-Join Queryという機能で使うらしい
<field name="_root_" type="string" indexed="true" stored="false"/>
本日のアジェンダ
(Solr4.10系を調べていた時)
・このschema.xmlのパラメータなんだろう
↓
・Block-Join Queryという機能で使うらしい
↓
・Block-Join って何……?
<field name="_root_" type="string" indexed="true" stored="false"/>
本日のアジェンダ
(Solr4.10系を調べていた時)
・このschema.xmlのパラメータなんだろう
↓
・Block-Join Queryという機能で使うらしい
↓
・Block-Join って何……?
↓
(ネットの海へ)
<field name="_root_" type="string" indexed="true" stored="false"/>
本日のアジェンダ
(Solr4.10系を調べていた時)
・このschema.xmlのパラメータなんだろう
↓
・Block-Join Queryという機能で使うらしい
↓
・Block-Join って何……?
↓
(ネットの海へ)
あまりにも情報がなかったので
調べてみてわかったアレコレをお届けします!
<field name="_root_" type="string" indexed="true" stored="false"/>
序章. 見よう!Block-Join
Block-Joinとは
• Solr4.5でリリース https://issues.apache.org/jira/browse/SOLR-3076
• Document(※)に親子関係を持たせ、
それらのIndex登録と検索を行うための機能
※以降、本資料内ではDocと略す
Block-Joinが出来る事(1)
• 関連付けたいDocをネストすることで
親子関係のDocとして登録できる
id:parent_1 Field1:Foo Field2:Bar
id:child_1 Field_C1:hoge Field_C2:any
Block-Joinが出来る事(1)
• 関連付けたいDocをネストすることで
親子関係のDocとして登録できる
※多重ネストは出来ない
id:parent_1 Field1:Foo Field2:Bar
id:child_1 Field_C1:hoge Field_C2:any
id:child_1_1 Field:xxx Field2:yyy
Block-Joinが出来る事(2)
• 親Docを検索軸に設定し
紐付く子Docを取得する
• 子Docを検索軸に設定し
紐付く親Docを取得する
基本的にはこの2パターン。
Block-Joinが出来る事(2)
●在庫(子)にケーキを持つ店舗(親)を検索したい
【親:店舗】 【子:在庫】
Block-Joinが出来る事(2)
●在庫(子)にケーキを持つ店舗(親)を検索したい
【親:店舗】 【子:在庫】
「コンビニ」を 結果として得る
Block-Joinが出来る事(2)
●スーパー(親)を検索し、在庫(子)を取得したい
【親:店舗】 【子:在庫】
Block-Joinが出来る事(2)
●スーパー(親)を検索し、在庫(子)を取得したい
【親:店舗】 【子:在庫】
「カステラ」と 「プリン」を 結果として得る
Block-Joinが出来る事(2)
ID 店舗名 品名 価格
1 A店 ○○ 100
2 A店 △△ 200
3 A店 ☓☓ 300
4 B店 ○○ 100
5 B店 △△ 200
<従来のIndex構成>
ID 店舗名(親) 品名(子) 価格(子) 1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300 2 B店
2-2 ○○ 100
2-3 △△ 200
<Block-Joinを使用したIndex構成>
●Block-Joinの検索軸イメージ
Block-Joinが出来る事(2)
ID 店舗名 品名 価格
1 A店 ○○ 100
2 A店 △△ 200
3 A店 ☓☓ 300
4 B店 ○○ 100
5 B店 △△ 200
<従来のIndex構成>
ID 店舗名(親) 品名(子) 価格(子) 1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300 2 B店
2-2 ○○ 100
2-3 △△ 200
<Block-Joinを使用したIndex構成>
●Block-Joinの検索軸イメージ
店舗でも品名でも 検索できるが 検索軸は並行構成
店舗でも品名でも 検索できるが 検索軸が分かれている
閑話休題:Block-Joinへの興味
●だいたいここまで調べた時点での感覚
閑話休題:Block-Joinへの興味
●だいたいここまで調べた時点での感覚
複数店舗の商品検索をするサイトとか コースの予約管理をするサイトで この機能をいい感じに使えないか?
閑話休題:Block-Joinへの興味
●だいたいここまで調べた時点での感覚
[既存の課題] ・Solrの1Doc内にマスタとサブの情報が混在している →マスタ情報更新時の影響がサブ情報にも発生する ・検索軸が複数存在しているケース →更新頻度の高い情報と低い情報の混在
複数店舗の商品検索をするサイトとか コースの予約管理をするサイトで この機能をいい感じに使えないか?
二章. できる!Block-Join
Block-Joinの基本ルール
1. schema.xmlに”_root_”があること
→親子関係を保証するためのFieldと値が
Index内に自動的にセットされる。
(ユーザが意識する必要はない)
<field name="_root_" type="string" indexed="true" stored="false"/>
Block-Joinの基本ルール
2. 全ての親Docには、
識別するための一律のFieldを
定義すること
id:parent_1 Field1:Foo Doc_Type:”parent”
id:child_1 Field_C1:hoge Field_C2:any
Block-Joinの基本ルール
2. 全ての親Docには、
識別するための一律のFieldを
定義すること
id:parent_1 Field1:Foo Doc_Type:”parent”
id:child_1 Field_C1:hoge Field_C2:any 識別用のField名・型・値は
検索軸となる親Docで一律であれば 何でもいい
<add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add>
Block-Joinのドキュメント登録
・XMLで登録する場合の例
<add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add>
Block-Joinのドキュメント登録
・XMLで登録する場合の例
(1)親Doc識別用Field
<add> <doc> <field name="id">1</field> <field name="title">Solr has block join support</field> <field name="content_type">parentDocument</field> <doc> <field name="id">2</field> <field name="comments">SolrCloud supports it too!</field> </doc> </doc> </add>
Block-Joinのドキュメント登録
・XMLで登録する場合の例
(2)親Docの中に子Docをネスト
Block-Joinのドキュメント登録
・SolrJで登録する場合の例 // Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 );
// Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 );
Block-Joinのドキュメント登録
・SolrJで登録する場合の例
(1)親Docは今までどおりSolrInputDocumentを生成して Field設定
// Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 );
Block-Joinのドキュメント登録
・SolrJで登録する場合の例
(2)子Docも独立した SolrInputDocumentとして Field設定
// Parent Doc SolrInputDocument product01 = new SolrInputDocument(); product01.addField( "id", "product01" ); product01.addField( "name", "car" ); product01.addField( "content_type", "product" ); // Children SolrInputDocument part01 = new SolrInputDocument(); part01.addField( "id", "part01" ); part01.addField( "name", "wheels" ); part01.addField( "content_type", "part" ); // Add children to parent product01.addChildDocument( part01 );
Block-Joinのドキュメント登録
・SolrJで登録する場合の例
(3)親Docのインスタンスが持つ addChildDocumentメソッドを呼び出し 子Docのインスタンスを紐付け
Block-Join 検索Query
1. 親Docを検索軸に設定し
紐付く子Docを取得する
基本構文:
q={!child of=<allParents>}<someParents>
Block-Join 検索Query
1. 親Docを検索軸に設定し
紐付く子Docを取得する
基本構文:
・allParentsは、親Docを識別するためのパラメータ
→親Docに一律定義したFieldと値をセット
・someParentsはメインQueryとなる検索条件
→{!child of}では親Docが検索軸となる
q={!child of=<allParents>}<someParents>
Block-Join 検索Query
2. 子Docを検索軸に設定し
紐付く親Docを取得する
基本構文:
q={!parent which=<allParents>}<someChildren>
Block-Join 検索Query
2. 子Docを検索軸に設定し
紐付く親Docを取得する
基本構文:
・allParentsは、親Docを識別するためのパラメータ
→子Doc用の識別Fieldではない
・ someChildrenはメインQueryとなる検索条件
→{!parent which}では子Docが検索対象となる
q={!parent which=<allParents>}<someChildren>
Block-Join 検索Query
ポイント:
・親Doc検索(parent which)でも
子Doc検索(child of)でも
パラメータは親Doc用の識別Fieldを使用
・親Doc識別パラメータは
単一条件しか指定できない。
(ORやAND、RangeQueryは使用不可能) {!parent which=DocType:parent}
{!child of=Group:2}
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名 品名 価格
1 A店 ○○ 100 2 A店 △△ 200
3 A店 ☓☓ 300
4 B店 ○○ 100
5 B店 △△ 200
<従来のIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名 品名 価格
1 A店 ○○ 100 2 A店 △△ 200
3 A店 ☓☓ 300
4 B店 ○○ 100
5 B店 △△ 200
→”A店”の店舗名が”A店X支所”に変わった…
<従来のIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名 品名 価格
1 A店X支所 ○○ 100 2 A店X支所 △△ 200
3 A店X支所 ☓☓ 300
4 B店 ○○ 100
5 B店 △△ 200
→”A店”の店舗名が”A店X支所”に変わった…
→店舗名が変わるだけで、属する
全てのDocの更新が必要だった
<従来のIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
→”A店”の店舗名が”A店X支所”に変わった
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店X支所
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
→”A店”の店舗名が”A店X支所”に変わった
→親Docしか持たない情報は
親Docの更新だけで完結する
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
→”A店”が廃止になった
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
→”A店”が廃止になった
→”A店”の親Docを削除すると……
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
→”A店”が廃止になった
→”A店”の親Docを削除すると……
→ネストしている子Docも連動して削除される
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 500
2 B店
2-2 ○○ 100
2-3 △△ 200
→ネストした子Docも独立したDoc扱いなので
子Docだけの更新や削除は可能
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
1-4 □□ 600
2 B店
2-2 ○○ 100
2-3 △△ 200
→ただし、今ある親子関係のDocに対して
子Docだけを追加することはできない
<親子関係を使用したIndex構成>
Block-Joinを使うメリット
■ Index更新量の削減
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
1-4 □□ 600
2 B店
2-2 ○○ 100
2-3 △△ 200
→ただし、今ある親子関係のDocに対して
子Docだけを追加することはできない
→追加するDocを含めた親子全てのDoc更新が必要
<親子関係を使用したIndex構成>
三章. 戦え!Block-Join
Block-Joinの気になるところ
(1) Block-Join Queryの検索軸に指定した
親/子Docは検索結果で同時に取得できない
Block-Joinの気になるところ
(1) Block-Join Queryの検索軸に指定した
親/子Docは検索結果で同時に取得できない
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
・親Docを検索軸にすると
子DocのFieldしか結果で得られない
Block-Joinの気になるところ
(1) Block-Join Queryの検索軸に指定した
親/子Docは検索結果で同時に取得できない
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
★既存のQueryを使う場合はネストの影響がなく
すべて独立したDocとして扱われる
Block-Joinの気になるところ
(1) Block-Join Queryの検索軸に指定した
親/子Docは検索結果で同時に取得できない
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-2 ○○ 100
2-3 △△ 200
★既存のQueryを使う場合は
ネストの影響がなく独立したDocとして扱われる
親/子Docを同時に検索したい場合は Block-Join Queryは使用しないこと
Block-Joinの気になるところ
(2) ネストした親子Doc間において
直接親子関係を取得する手段がない
Block-Joinの気になるところ
(2) ネストした親子Doc間において
直接親子関係を取得する手段がない
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-1 ○○ 100
2-2 △△ 200
検索軸を親Docとして「A店 OR B店」とした時
ID 店舗名(親) 品名(子) 価格(子)
1 A店
1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店
2-1 ○○ 100
2-2 △△ 200
Block-Joinの気になるところ
(2) ネストした親子Doc間において
直接親子関係を取得する手段がない
検索軸を親Docとして「A店 OR B店」とした時
Block-Join Queryでそれぞれの子Docがヒットする
子Docがどちらの店舗に属しているかがわからない
Block-Joinの気になるところ
(2) ネストした親子Doc間において
直接親子関係を取得する手段がない
ID 店舗名(親) 店舗ID(子) 品名(子) 価格(子)
1 A店
1-1 1 ○○ 100
1-2 1 △△ 200
1-3 1 ☓☓ 300
2 B店
2-1 2 ○○ 100
2-2 2 △△ 200
→必要であれば、親子Doc間で情報を共有するように
Index構成を作る
この例では、子に親DocのIDを持たせることで
親Docの情報を検索して引っ張ることが可能となる。
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
q={!parent which=DocType:店舗} 品物:○○
→子Docに品物”○○”を持つ店舗(親Doc)を検索する
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
q={!parent which=DocType:店舗} 品物:○○
→子Docに品物”○○”を持つ店舗(親Doc)を検索する
→メインQueryでは、子Docを検索対象とするため
親Docを絞り込む条件を記述することが出来ない
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ
→絞り込みたい親Docを対象とした
FilterQueryを追加する。
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
ID 店舗名(親) 店舗種別(親) 品名(子) 価格(子)
1 A店 コンビニ 1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店 ドラッグストア 2-1 ○○ 100
2-2 △△ 200
q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
ID 店舗名(親) 店舗種別(親) 品名(子) 価格(子)
1 A店 コンビニ 1-1 ○○ 100
1-2 △△ 200
1-3 ☓☓ 300
2 B店 ドラッグストア 2-1 ○○ 100
2-2 △△ 200
→親Docはfqの店舗種別:コンビニで絞りこまれる
“A店”だけがヒットするようになる
q={!parent which=DocType:店舗} 品物:○○ &fq=店舗種別:コンビニ
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
補足:
FilterQueryは親子Docの区別なく
フィルタリングしてDocを抽出する
Block-Joinの気になるところ
(3) Block-Join Queryでは検索軸に指定した
親/子Docの絞り込みができない
補足:
FilterQueryは親子Docの区別なく
フィルタリングしてDocを抽出する
→検索軸が親/子どちらの場合でも使用できる。
(検索軸側に対して、本来のFilterCache用途で
使用することもできる)
Block-Joinの気になるところ
(4) 検索軸に使用した親/子DocのFieldから
Facetを取得することが出来ない
Block-Joinの気になるところ
(4) 検索軸に使用した親/子DocのFieldから
Facetを取得することが出来ない
・Facetは検索結果から作成するため
Block-Joinの気になるところ
(4) 検索軸に使用した親/子DocのFieldから
Facetを取得することが出来ない
・Facetは検索結果から作成するため
→親/子Doc相互でFacet用に情報を持たせておく
(複数の子→親ならマルチFieldなど)
→子Docを検索軸にした場合の対応については
パッチが提案されている。 https://issues.apache.org/jira/browse/SOLR-5743
Block-Joinの気になるところ
(5) Block-Join Queryでは
スコア計算は全て0.0になる
Block-Joinの気になるところ
(5) Block-Join Queryでは
スコア計算は全て0.0になる
・Lucene内では
スコア計算のパターンはいくつか実装されている
しかし……
検索軸 タイプ 意味
子Doc
None スコア計算なし(デフォルト)
Avg 子Docのスコア平均
Max 子Docのスコア最大値
Total 子Docのスコア合計
親Doc false スコア計算なし(デフォルト)
true 親Docのスコアを使う
Block-Joinの気になるところ
(5) Block-Join Queryでは
スコア計算は全て0.0になる
・Solr上のBlock-JoinQueryパーサ処理にて
None(false)で固定でされている
<BlockJoinParentQParser.java>
protected Query createQuery(Query parentList, Query query) { return new ToParentBlockJoinQuery ( query, getFilter(parentList), ScoreMode.None ); }
Block-Joinの気になるところ
(5) Block-Join Queryでは
スコア計算は全て0.0になる
・Solr上のBlock-JoinQueryパーサ処理にて
None(false)で固定でされている
<BlockJoinParentQParser.java>
ソースコードをいじらない限り
Block-JoinQueryの結果でスコア取得する手段はない
protected Query createQuery(Query parentList, Query query) { return new ToParentBlockJoinQuery ( query, getFilter(parentList), ScoreMode.None ); }
終章. 進撃のBlock-Join
まとめ
• Block-Joinは
「検索軸」を明確に分けるための機能
まとめ
• Block-Joinは
「検索軸」を明確に分けるための機能
→当初の課題であった
マスタ情報とサブ情報の
検索・更新軸を明示的に分けるという
用途では使えそうな気がする。
まとめ
• Block-Joinは
「検索軸」を明確に分けるための機能
→当初の課題であった
マスタ情報とサブ情報の
検索・更新軸を明示的に分けるという
用途では使えそうな気がする。
・サブの要素数に左右されにくくなるので
複雑なクエリをスマートにしやすい、とか
Field設計を容易にしやすい、とか……。
課題
・Block-Join Queryがクセモノ
要件によっては、ここまで述べてきた通り
ひと工夫が必要になる。
・パフォーマンスについては未知数……。
課題
商用サイトでの導入と実証検証を目指して 引き続き調査・対応を進めていきます。
・Block-Join Queryがクセモノ
要件によっては、ここまで述べてきた通り
ひと工夫が必要になる。
・パフォーマンスについては未知数……。