CrowdWorksのElasticsearch利用

この記事は[CrowdWorks Advent Calendar 2016](http://qiita.com/advent-calendar/2016/crowdworks)、15日目の記事です。

今回はCrowdWorksにおけるElasticsearch利用について紹介したいと思います。

## はじめに

CrowdWorksはRailsで作られた仕事のマッチングサービスです。
働くワーカーさんがお仕事を探す場合や、お仕事を依頼したいクライアントさんがワーカーさんを探すための検索にElasticsearchを利用しています。

![job_offer-search-half.png](https://qiita-image-store.s3.amazonaws.com/0/43244/4e6644ae-a66d-3cb7-34cf-72ca0bb05758.png)


検索対象のデータはMySQL（Amazon RDS）に永続化されており、これをElasticsearchに同期する形で利用しています。

## Elasticsearchの運用

Elasticsearchの運用は[Elastic Cloud](https://www.elastic.co/jp/cloud)を利用しています。

クラスタのスケールアップやバージョンアップ、プラグインの管理やセキュリティ設定がブラウザから簡単に設定できて便利です。

![Elastic_Cloud 3.png](https://qiita-image-store.s3.amazonaws.com/0/43244/94d2706d-0224-23dc-4ba4-68181b23f786.png)
![Elastic_Cloud 4.png](https://qiita-image-store.s3.amazonaws.com/0/43244/cb83a892-6b9c-c556-9179-271a8d86f483.png)

Elastic社本家のサービスだけあって新バージョンがリリースされた場合はリリースとほぼ同時に使うことができます（現在利用可能な最新バージョンは[5.1.1](https://www.elastic.co/blog/elasticsearch-5-1-1-released)）。

サポートは英語ですが問い合わせするとちゃんと対応してくれます。つい先日までElasticsearchがHTTPステータス502などでつながらないことがときどき起きており、こちら側では対処できないため[問合せたところ](https://discuss.elastic.co/t/getting-502-and-504-errors-from-elastic-cloud/65577)調査していただき直してもらいました。

その他、ユーザー辞書のアップロードを可能にするといったこともサポート経由で[お願いしています](https://discuss.elastic.co/t/please-enable-the-feature-that-uploading-scripts-and-dictionaries/67870)。こちらはデフォルトですべてのアカウントで有効にすべきかElastic社内で検討中のようですが、直近の対応はないようです（[参考](https://discuss.elastic.co/t/how-to-upload-scripts-and-dictionaries/64632/2)）。

### バージョンアップ時の注意

UIで簡単にバージョンアップできるのは便利なのですが、実行するとマイナーアップデートであっても短い時間ですがサービスに繋がらなくなるので危険です。実際に先日、バージョン2.4.1から2.4.2へのアップデート時にこれをやってしまい10秒〜1分程度サービスが利用できないという障害になってしまいました。

![Elastic_Cloud.png](https://qiita-image-store.s3.amazonaws.com/0/43244/0c609dd3-22ec-437d-415c-8171480ba7f2.png)

※追記: Elastic Cloudのエンジニアの方に聞いたところマイナーバージョンアップでは通常起こらないので原因調査のためフォーラムで問い合わせて欲しいとのことでした。

## Railsアプリケーション側の実装

RailsからのElasticsearch利用は[elasticsearch-rails](https://github.com/elastic/elasticsearch-rails)、[elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model)を利用しています。
特にelasticsearch-modelによってマッピングを定義することで、メタデータとしてのマッピングの反映とそのマッピングを元にした実データの登録を一貫して行うことができて便利です。


### データ更新と同期

検索対象のデータが更新された場合は、各レコードの更新毎にElasticsearch側に同期しています。
更新用のジョブを登録し、そのジョブをアプリケーションとは別のワーカープロセスが実行する形です。

![遅延同期.png](https://qiita-image-store.s3.amazonaws.com/0/43244/4d421e65-2569-c75a-f859-50bdb378538c.png)

ただ、上記の同期がうまくいかないことがあるためデータがきちんと同期されているかをチェックし、同期されていなければ同期するバッチを定期的に流しています（20分ごと）。現状のデータ同期はこれに頼っていることが多いです。

### マッピングの更新

検索対象のフィールドを追加したいなどマッピングの更新したい場合は再度インデックスを作り直す必要があるため、アプリケーション側では直接インデックス名を参照せず、エイリアスを参照するようにしています（インデックスにはタイムスタンプ付きで名前を付ける）。

![エイリアス参照.png](https://qiita-image-store.s3.amazonaws.com/0/43244/7457eb3b-e629-6256-7dbd-85af3f17dc13.png)

マッピング変更によるインデックス再作成は、次のような処理をスクリプトで行っています。

* 新しいマッピングで新たにインデックスを作る（データはMySQLから同期）
    * このとき開始時間を記憶しておく
* インデックス生成がおわったらエイリアスを新インデックスを指すようにする
* 新インデックス作成開始時点から更新されたレコードがないかをチェックし、更新されたレコードは新インデックスに反映させる
* すぐ検索できるよう[Refresh](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html)を実行する

### 検索の実行

検索に関してはアナライザに[kuromojiプラグイン](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)を利用したキーワード検索とその他フィルタリング条件を加えたシンプルなもので、これまで特にチューニングせずにやってきました。
現在スコアリングやユーザー辞書による検索精度の向上を進めていますので、何か成果が出た際にまた紹介できればと思います。

また、条件の数や組み合わせが増えてメンテナンスしにくくなっているのが現状の課題としてあります。

## まとめ

簡単にですがCrowdWorksのElasticsearch利用について紹介しました。

明日は@ouraさんによる「いろいろやるチーム」です。よろしくお願いします。