Apache Solr

 

solrのフィールド設計

solrのフィールド設計について検討します。落とし穴を事前に知っておくと、後でハマる事も少なくなります。フィールド設計によってデータ量とインデキシング速度が変わるので慎重に設計しましょう。

新サイト、tree-mapsを公開しました!!

tree-maps: 地図のWEB TOOLの事ならtree-mapsにお任せ!

地図に関するWEB TOOL専門サイトです!!

大画面で大量の緯度経度を一気にプロット、ジオコーディング、DMS<->DEGの相互変換等ができます!

◯ 広告

通常のフィールド(以降、static fieldと呼びます)とdynamic fieldのどちらを使うか。

一番最初に検討するべき部分です。

まずは両者をよく知っておく必要があります。

static fieldはsolrの標準のフィールドです。

私は以下の3種類についてのみstatic fieldにしています。

  • uniqueKeyフィールド
  • timestampフィールド
  • _version_フィールド

uniqueKeyフィールドは必ず1個必要なので、これは必然的にstatic fieldになります。

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 

timestampフィールドは以下のようにschema.xmlを設定し、自動的にtimestampが設定されるようにします。

<field name="timestamp" type="date" indexed="true" stored="true" default="NOW" multiValued="false"/>

_version_は、solr4.0から導入された必須フィールドです。

必須なのでこれも必然的にstatic fieldになります。

<field name="_version_" type="long" indexed="true" stored="true"/>

これら以外は全てdynamic fieldにします

static fieldの場合は1フィールド毎にschema.xmlに定義が必要です。

dynamic fieldの場合は、「*_int」という感じに命名規約を定義することで、動的定義を可能にしています。

以下のようにstored、indexed、multiValuedの組み合わせを型毎に定義します。

<dynamicField name="*_str_stored" type="string" indexed="false" stored="true" multiValued="false"/>
<dynamicField name="*_str_stored_list" type="string" indexed="false" stored="true" multiValued="true"/>
<dynamicField name="*_str_indexed" type="string" indexed="true" stored="false" multiValued="false"/>
<dynamicField name="*_str_indexed_list" type="string" indexed="true" stored="false" multiValued="true"/>
<dynamicField name="*_str" type="string" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_str_list" type="string" indexed="true" stored="true" multiValued="true"/>

<dynamicField name="*_bool_stored" type="boolean" indexed="false" stored="true" multiValued="false"/>
<dynamicField name="*_bool_stored_list" type="boolean" indexed="false" stored="true" multiValued="true"/>
<dynamicField name="*_bool_indexed" type="boolean" indexed="true" stored="false" multiValued="false"/>
<dynamicField name="*_bool_indexed_list" type="boolean" indexed="true" stored="false" multiValued="true"/>
<dynamicField name="*_bool" type="boolean" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_bool_list" type="boolean" indexed="true" stored="true" multiValued="true"/>

<dynamicField name="*_tint_stored" type="tint" indexed="false" stored="true" multiValued="false"/>
<dynamicField name="*_tint_stored_list" type="tint" indexed="false" stored="true" multiValued="true"/>
<dynamicField name="*_tint_indexed" type="tint" indexed="true" stored="false" multiValued="false"/>
<dynamicField name="*_tint_indexed_list" type="tint" indexed="true" stored="false" multiValued="true"/>
<dynamicField name="*_tint" type="tint" indexed="true" stored="true" multiValued="false"/>
<dynamicField name="*_tint_list" type="tint" indexed="true" stored="true" multiValued="true"/>

面倒ですが、全ての型の分を最初に用意してしまえば、以降はschema.xmlを編集する事はほとんどなくなります

開発時は頻繁にフィールドの追加・更新・削除があるので、dynamic fieldは非常に効率がよいです。

他にも、dynamic fieldだと運用後も無駄にschema.xmlを編集する必要がなくなり、メンテコストも下がります。

一見するとdynamic fieldは万能で非常に使い勝手がよく見えます。

しかし、実は落とし穴があるのです。

それは、フィールド名変更の更新タイミングにあります。

フィールド更新の反映タイミング・フローについては以下の通りです。

static field dynamic field
1 schema.xmlを修正。 @Fieldアノテーションを修正。
2 tomcatを再起動。
フィールド名は更新される。
サーチャーは更新後のフィールド名を使用。
tomcatを再起動。
フィールド名は更新されない
サーチャーは更新前のフィールド名を使用
3 - インデックス生成。
4 - インデックス生成後のcommitでインデックス名が変更される。

static fieldは2ステップで全て反映されます。

dynamic fieldはtomcat再起動後が危険な状態になります。

例えば「staff_name」を「staff_full_name」に変更するとします。

tomcat再起動時点ではサーチャーは「staff_full_name」を使うインデックスは「staff_name」のままなのです。

この状態でサーチャーが検索を実行すると、staff_full_nameはunknown fieldですよエラーが発生するのです。

ではインデックス名がいつ更新されるかというと、インデックス生成後になります。

dynamic fieldはschema.xmlに名称が定義されないので、commitするまでインデックス名が解らないので反映されません

この問題を回避するには、1件だけインデックス生成する方法があります。

1件更新しただけでは更新前と更新後のインデックスが共存し、検索もヒットしませんが、エラーは回避できます。

その場合、インデックス全件生成時の「delete query *:*」で旧インデックス削除、新インデックス生成され、ゴミが削除されます。

これを考慮すると、dynamic fieldはフィールド名を慎重に設計する事が重要です。

java側の型は非常に重要です。

java側の型設計のポイントはsolrはnull値は保存できず検索もできない点にあります。

javaの型、プリミティブ型(int)とオブジェクト型(Integer)は以下のような使い分けをするといいと思います。

stored=true、indexed=false オブジェクト型にします。プリミティブ型だと空文字や0等の無駄な値が保存されてしまいます。
stored=false、indexed=true プリミティブ型にします。null値は検索にヒットしないので、プリミティブ型で値必須状態にします。
stored=true、indexed=true プリミティブ型にします。null値は検索にヒットしないので、プリミティブ型で値必須状態にします。

indexedしていない項目でソートしたい要件、よくありますね。

例えば検索は「割引前価格」で、ソートは「割引後価格」にしたい、等という謎要件も沢山あります。

この要件を満たす為には、stored=false,indexed=trueのソート専用フィールドを作る事になります。

ソート用フィールドが沢山できると、当然インデックス生成時間は長く、ファイルサイズも膨らみます。

ソートに関してはオブジェクト型にして極力インデクシングしないようにしましょう。

前述のようにnull値は値として保持されないので、これを意識して高速化・軽量化を目指します。

フィールドの要素にはsortMissingLastがあり、これも有効活用できます。

sortMissingLastはnull値(ソート項目に値が無い場合)を先頭にするか末尾にするかを制御できます。

例えばsortMissingLast=trueを利用して、先頭にしたいデータに1を設定、それ以外はnull値にする事で、ソートが可能です。

この小技のメリットはやはりインデックスを少なく・小さくしてインデクシングを高速化・軽量化できる点です。

multivaluedのフィールドはソートできません。技術的には可能らしいですが、非常に低速になるとのこと。

これは以外と見落としがちなので注意が必要です。最初から意識しておく必要があると思います。

◯ 広告