Elasticsearch v1.7 -> v2.4 -> v5.5 段階的にバージョンアップした話し


この記事は[Elasticsearch Advent Calendar 2017](https://qiita.com/advent-calendar/2017/elasticsearch)の12/12分の記事です。

Elasticsearchは1.xから2.x、そして5.x(今月にはもう6が出ましたね)と年々バージョンアップしてきました。そんななかまだ1.x、2.x系を使っているという方は多いのではないでしょうか。Elasticsearchはバージョンアップごとに大きくクエリやマッピングが変わります、またサービスの稼働率を考えるとクラスタのリスタートやホットスワップは難しいシステムとなっていたりしていると思います。

そんな障壁を超えるほどの魅力が5.xにはありました。弊社の検索システムではElasticsearchのバージョンアップにより平均レスポンスタイムが2倍も速くなりました。それだけで十分アップグレードする価値はあるのではないでしょうか。

## 要点

- 検索クエリごとにきちんとテストを書きましょう、Elasticsearchを実際に使ったCIを組み立てる
- テスト環境のElasticsearchのバージョンアップしてひたすらGreenを目指す
- Deprecated クエリをちゃんと書き換えましょう、[Deprecation log](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/settings.html#deprecation-logging)を活用する


## 背景

弊社クックパッドは国内のみならず[全世界21言語、67カ国](https://cookpad.com/us/regions)にレシピサービスを提供しています。21以上の言語をサポートするグローバルプラットフォームのレシピ検索にElasticsearchが使われています。言語ごとにindex mappingを用意し、その言語、地域に対して最適な検索を提供しています。

長らく、Elasticsearch v1.7を使っていたのだが、サービスの規模が大きくなるに連れパフォーマンスが気になってきました。それを今年にかけて、段階的にv1.7ー>v2.4ー>v5.5にバージョンアップしました。メジャーバージョンアップごとにたくさんのmapping, query DSLの変更があり、それらをどうやって効率的に解消していったかをはなしたいと思います。

## アップグレード手順

- Elasticsearch branch 作成
- ローカルでテストが通るまでコードを直す
- マニュアルチェック(テストでカバーされていないバグ発見)
- ステージング環境にESクラスタを立ててコード&インフラ周りのチェック
- プロダクションデプロイ(hot swap)
- 追加の改善, fix, deprecatedクエリ書き換え

クックパッドでは、検索の機能ごとにElasticsearch上で実際にクエリを走らせて挙動を確認するインテグレーションテストを用意しています。なのでElasticsearchをバージョンアップしてテストを走らせるだけで大体の問題箇所を探すことができました。

テストが走るとき、まずテスト用のindexが作成できないと始まらないので真っ先にmappingを直します。それからquery DSLをひたすら直していきます。ElasticsearchドキュメントのBreaking changesとひたすらにらめっこです。
https://www.elastic.co/guide/en/elasticsearch/reference/2.4/breaking-changes-2.0.html
https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking-changes-5.0.html



## 1.7 -> 2.4 のときの主な変更
### mapping changes
- 名称変更: `index_analyzer`, `position_offset_gap` など 
	- https://www.elastic.co/guide/en/elasticsearch/reference/2.4/breaking_20_mapping_changes.html
- マッピングのなかで同じフィールド名が使えなくなった
  - https://www.elastic.co/guide/en/elasticsearch/reference/2.4/breaking_20_mapping_changes.html#_conflicting_field_mappings
- stopwords, custom_stemファイルなどをElasticsearchの設定ディレクトリ以外から読めなくなった
  - Javaのセキュリティアップデートにより外部ファイルを指定したディレクトリからしか読み込めなくなりました。
  - stopwordsなどのカスタムファイルを`/etc/elasticsearch/`内に置いた

### その他変更
- gem update: https://rubygems.org/gems/elasticsearch/versions
- CI環境をv2.4にセットアップ
- gemアップデートによるpercolateクエリの不具合
  - multi percolateクエリを`Elasticsearch::API::Utils.__bulkify(body)`というメソッドを直接たたいてビルドしていたのがだめになった。
  - 以前percolateクエリにおいてbulkifyが動いていたのが、[この変更](https://github.com/elastic/elasticsearch-ruby/commit/69fc85dd8d2fa4bbd355b8105f4ad1e144dae56c#diff-d75db13ea97575611e4291898d5ed72aR83)により出来なくなっていた :innocent: 

この通り、ほとんどマッピング変更でCIもパスし、マニュアルチェックで見つかったバグも直しアップグレードできました。しかし、お気付きの通りクエリの変更がほとんどありません。実は殆どのクエリがdeprecatedになっていたが一応サポートしていたため正常に動いていただけでした。これがv5.5アップグレードのときに大きな負債となりました。

## 2.4 -> 5.5 のときの主な変更

上述の通り、v1.7 -> v2.4の際にクエリの変更をほとんどしませんでした。v2でdeprecatedになったクエリがなんとか動いていたという状態でした。[それがv5になってdeprecatedクエリたちがすべて使えなくなって](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_search_changes.html#_deprecated_queries_removed)、クエリエラーがv2変更のエラーなのかv5変更のエラーなのかわからず、v2とv5のドキュメントを行き来することになりました。 :sweat_smile: 

このことから学び、v5へのアップグレードではElasticsearchのdeprecation logをもとにdeprecatedになったAPIやクエリを直しました。[Deprecation log](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/settings.html#deprecation-logging)はバージョンアップでdeprecatedになったクエリのログを出してくれます。次のバージョンでなくなる予定のクエリなので直しておくと、バージョンアップがスムーズに行くと思います。

### mapping changes

- `type: "string"` to `type: "keyword"`
  - Significant terms aggregation機能が`keyword`フィールドを推奨
  - https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_aggregations_changes.html#_significant_terms_on_numeric_fields
- `fielddata: true`
- Suggestion変更
- `multi_field` removed
- `percolator`の大きな変更によりpercolator用のマッピングを追加
	- https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_percolator.html

### クエリ変更
- v2.xで廃止されたfilterをqueryに変更
  - function_score クエリ書き換え
  - filterクエリ書き換え

```js
{
  filter: { bool: { must: filters } },
  query: [queries],
  functions: [functions]
}
```
->

```js
{
  query: { bool: { must: [filters, queries] } },
  functions: [functions]
}
```

- なぜかv2で使えていた時間unitの `1w` が使えなくなってた :innocent:
  - https://www.elastic.co/guide/en/elasticsearch/reference/5.5/common-options.html#time-units
- v2.xでdeprecatedになった`filtered` クエリがv5.xで削除されたので書き換え
  - `filtered: query` -> `bool: must`
  - https://www.elastic.co/guide/en/elasticsearch/reference/2.4/breaking_20_query_dsl_changes.html#_literal_filtered_literal_query_and_literal_query_literal_filter_deprecated
- `execution "or"` deprecated
- Suggestion completionの大型変更によりレスポンスボディが変わってた
	- https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_suggester.html
- `size: 0` in terms aggregation function is deprecated
- GroovyをPainlessへ書き換え
	- (Painlessのドキュメント少ない :sweat: )
- `scan & scroll` でreindexしていたのを、v5から新設されたreindex APIに置き換えた

### その他
- suggestion query に約8000文字以上のリクエストが来るとクラスタが落ちる :innocent: 
  - ES6で直ったらしいです [ref1](https://github.com/elastic/elasticsearch/issues/24553), [ref2](https://github.com/elastic/elasticsearch/issues/25502#issuecomment-313302093)
  - これは本当に皆さんも気をつけて下さい、アプリ側で文字制限を設けました
- 起動オプションが変わった
  - `-D es.network.host=xxx` to `-E network.host=xxx`
- 他に特定の言語向けの自然言語処理プラグインを新しいバージョン向けに自前で書き直す必要がありました

### v5.xでdeprecatedになった機能の書き換え

上記の変更でテストも通り、プロダクションデプロイできました。バージョンアップ後にdeprecation logをもとに以下のAPIを書き換えました。

- [Percolator API to query](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_percolator.html)
- [Suggestion API to query](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/breaking_50_suggester.html)
- [Reindex API](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/docs-reindex.html)


# その他tips

Elasticsearchに実際に投げられているクエリをproxyツールなどでキャプチャして、Cerebroなどから直接投げて、直るまでCerebroを使ってデバッグするとやりやすかったです。

# 所感
Elasticsearchはバージョンアップごとにアップグレードしやすくなってきていますね。ES6ではrolling upgradeも対応しましたしね。Deprecation logがデフォルトでonになっていたり、間違ったクエリを投げたときちゃんとどこが悪いのかを返してくれるようになってきていました。そのためデバッグもしやすかったです。
公式ドキュメントの充実しているのもとても助かります。バージョンアップで変わったところをどう書き換えればいいかなど、ちゃんと書かれています。

今回はインフラ周りの話しまでは触れることはできませんでした。サービスの特徴上ホットスワップはある程度やりやすかったのもあります。
また具体的な話しが少なく、いろいろ詳細部分がもれてわかりにくい記事になっているかもしれません m(_ _)m それらについてはコメント等で聞いていただければと思います。


明日は"Elasticsearchのための新しい形態素解析器「Sudachi」"についてですね。アドベントカレンダーこれからも楽しみにしています。