SolrでのdocValuesとstoredの使い分け

みなさんこんにちは、この記事は Solr Advent Calendar 2016 の 14日目です。
(この記事を書いている時点でのSolrの最新バージョンは、6.3.0です)

# はじめに
Solr6系から、サンプルスキーマでもデフォルトとなった、docValuesですが、storedとどちらを使えば良いか迷う事もあるのではないでしょうか。
今回は、docValuesとstoredの使い分け、docValuesを使用する時のハマリどころなどについて書いていこうと思います。

# stored field
stored fieldは、表示用のデータを格納しておくためのField Typeです。
docValuesが使用可能になるまでは、検索結果に値を含めたい場合は、もれなく`stored="true"`にする必要がありました。
また、docValuesが使用可能になってからも、[AtomicUpdate](https://cwiki.apache.org/confluence/display/solr/Updating+Parts+of+Documents#UpdatingPartsofDocuments-AtomicUpdates)の条件として、`stored="true"`が必須であったために、全フィールドを`stored="true"`にする必要があるケースもあったかと思います。

# docValues
[docValues](https://cwiki.apache.org/confluence/display/solr/DocValues)は、もともと、カラム志向(document-to-field)のデータ構造として導入されました。
導入の動機は、以下のリンクに詳しいですが、FieldCacheの代替、stacked-approachによる部分更新の実現（残念ながら、[こちら](https://issues.apache.org/jira/browse/SOLR-5944)はSolrではまだマージされていませんが)を主目的としていたように見受けられます。
[DocValues aka. Column Stride Fields in Lucene 4.0 - By Willnauer Simon](http://www.slideshare.net/lucenerevolution/willnauer-simon-doc-values-column-stride-fields-in-lucene)
FieldCacheと同等の構造を効率よく構築できるようになったおかげで、facet,sort,groupingといった処理が、従来より高速に処理できるようになります。また、storedのようにdocValuesの値を取得することも可能になります。
ただし、指定可能なField Typeに制限があります。具体的に指定可能なField Typeは、公式の[cwiki](https://cwiki.apache.org/confluence/display/solr/DocValues)を参照してください。

# storedとdocValuesの違い
もちろん、`stored="ture"`だけのFieldでは、facetなどは実現できないので、色々と機能的な違いはあるわけですが、schemaを書くときに、こう思ったことがある方もいるのではないかと思います。
- **この`stored="true"`は、`docValues="true"`でも良いのでは？**
- **`stored="true"`と`docValues="true"`を両方設定しているが、storedは必要だろうか？**

ここではこう言った疑問に答えていければと思います。

## AtomicUpdateには、`stored="true"`が必須という誤解
最近(2016/10)まで、solrのcwikiでは、AtomicUpdateの為には、copyfieldを除いて、`stored="true"`であるべき、という記述が残っていました。
[cwikiの更新履歴](https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=34022607&selectedPageVersions=30&selectedPageVersions=29)
実際には、[SOLR-8276](https://issues.apache.org/jira/browse/SOLR-8276)で、docValuesの対応が完了しているので、Solr-5.5,Solr-6.0以上であれば、`stored`か、`docValues`のどちらを`true`にすることで、AtomicUpdateには対応が可能です。(実際には後述するようにSolr-6.3以上をおすすめします)
storedと、docValuesを両方設定している場合は、AtomicUpdateのためにstoredが必要というケースが多いのではないかと思うので、その場合には、docValuesのみに削ることが可能です。
(補足になりますが、AtomicUpdateに関しては、useDocValuesAsStored=falseでも問題ありません)

*Solr-6.0以降、AtomicUpdateに、`stored=true`は必須ではない*

## flパラメータ指定時の挙動
docValuesの挙動は、storedに比べて少し複雑なので、ここにまとめておきます。
挙動がこうなった経緯は、[SOLR-8220](https://issues.apache.org/jira/browse/SOLR-8220)にまとまっているので、興味がある方は見てみると良いかもしれません。

Field Typeと、fl指定時の検索結果時の返却有無

| Field Type | glob指定(fl=*) | fieldを直接指定(fl=id) |
|:-:|:-:|:-:|
| stored  |  o | o  |
| docValues with useDocValuesAsStored=true  |  o | o  |
| docValues with useDocValuesAsStored=false  | x  | o  |

基本的には、useDocValuesAsStored=trueにしておけば良いと思いますが、storedから移行する場合は、返却結果の挙動が変わってしまう可能性があるので、気をつけましょう。また、storedと、docValuesを両方指定している場合は、storedから優先して取得されます。次項のMultiValueの挙動の違いなどもあるので、注意してください。

*useDocValuesAsStored=trueを指定すると、storedと同じ挙動になる*

## MultiValueの扱いの違い
docValuesでは、MultiValueの取得時に以下の制限があります。

- storedでは、追加した順だが、docValuesでは、sort順になる(2,3,1 -> 1,2,3)
- docValuesでは、同じ値を重複して持つことができない(1,1,1 -> 1)

これは、docValuesが、内部的に値をsort列で管理していることに起因しています。
上記の挙動では、要件が満たせない場合、storedを使用する必要があります。

*MultiValueで、順序を保持したい or 同じ値を複数保持したい場合はstoredを使用する*

## \_version_ field
\_version_ fieldは、Optimistic Concurrencyに必要な、Solrが用いる特殊なフィールドです。
\_version_ fieldに関しては、[SOLR-8831](https://issues.apache.org/jira/browse/SOLR-8831)によって、docValuesのみの指定が可能になっています。特に、今までのチケットと違い、Solr5.5系へのバックポートがされていないので、5系を使用している場合は注意した方が良いでしょう。

*5系では、\_version_ fieldは、non storedなdocValuesにはしない*

## docValuesのパフォーマンス

[本アドベントカレンダー](http://qiita.com/advent-calendar/2016/solr)の[5日目](http://qiita.com/tkondo/items/e4757193b5588c3c1627)で、[tkondo](http://qiita.com/tkondo)さんが、まとめてくれていますが、docValues使用時にAtomicUpdateのパフォーマンスが遅くなる事象がありました。特にIndexが大きいなど、条件によっては、かなりの性能劣化が認められることもありました。
これは、[SOLR-9592](https://issues.apache.org/jira/browse/SOLR-9592)によって、Solr-6.3以降で解消されています。(上で、6.3以上がおすすめと書いたのはこのため)
本件に関しては、先日の第19回LuceneSolr勉強会にて、詳細を発表してきたので、興味のある方はそちらも参照してもらえればと思います。

> 第19回LuceneSolr勉強会
[SolrのAtomicUpdateを50000倍速くした話](http://www.slideshare.net/TakahiroIshikawa6/solratomicupdate50000)

*パフォーマンスが懸案事項で、AtomicUpdateを酷使する場合は、Solr-6.3以降を使用する*

## Indexサイズ
検索性能より、Indexのサイズが懸案事項の場合storedを用いた方が値をまとめて圧縮するので、結果的にIndexサイズが小さく納まる傾向にあると思います。しかし、docValuesでも、sparseな場合に効率良く圧縮する([LUCENE-6863](https://issues.apache.org/jira/browse/LUCENE-6863))修正が入っていたりするので、一般にどちらの方がサイズが小さくおさまるかというのは非自明です。ここは、各々の環境で検証してもらう方が良いでしょう。

*Indexのサイズはケースバイケースに思われるので、各々の環境で検証すること*

# まとめ
以上をまとめると、Solr-6.3以降では、storedの役割は以前より限定的になったのではないかと思います。
storedを指定する場合は、主に以下のようなケースが該当します。

- docValuesが対応していないField Typeである（text_jaなど)
- MultiValueで、順序もしくは、同じ値を複数保持する必要がある
- Indexサイズが懸案事項で、実際に比較した結果、storedに優位性がある

以上の条件に当てはまらなければ、Solr-6.3以降であれば、docValuesの使用を制限するものはないかと思います。(自分も全ての機能を試したわけではないので、抜けがあったらコメントしていただけるとありがたいです)

以上、storedと、docValuesに焦点を当てて説明をさせていただきましたが、いかがでしたでしょうか。この記事が皆様のお役に立てたら幸いです。