Elasticsearch実運用時の注意点とアンチパターンまとめ
Elasticsearch実運用時に個人的に気をつけている点をまとめました。 自分でも整理できていない部分がありますので、間違っている点・追加した方が良い点などありましたら教えていただけると非常に助かります。
目次
インデックス・スキーマ設定
スキーマを事前に定義しておく
Elasticsearchはスキーマレスじゃないのか!といきなり突っ込まれそうな感じですが、個人的にはデータ投入前にスキーマを厳密に定義しておくのをお勧めします。
理由としては、主に以下の2点です。
- スキーマを定義せずに投入されたデータはElasticsearchが型を判断するためエラーの元になりやすい
- not_analyzed・doc_valueなどの詳細設定(後述)を行ったほうが性能が良くなる
_allの使用は必要な時のみ行う
デフォルトで全フィールドのデータが入る_allというメタフィールドがありますが、ディスク容量・性能の観点から全フィールドを跨いだ検索を行う予定が無い場合は無効にしたほうが良いです。
{ "mappings" : { "sample_type" : { "_all" : {"enabled" : false} } } }
部分一致検索をしないstringフィールドにはnot_analyzedをつける
stringフィールドはデフォルトではインデックス時に形態素解析などの処理が行われてしまうため、完全一致検索時に想定しない結果が返ってきたりインデックス処理が重くなったりしてしまいます。
完全一致検索のみを行いたいstringフィールドに対しては、マッピング時にnot_analyzedをつけることを推奨します。
"string_field" : { "type" : "string", "index" : "not_analyzed" }
doc_value:trueをつける
マッピングの際にdoc_values:trueを設定すると、cacheをメモリではなくディスクに乗せることができます。メモリに乗せる場合よりも20%程低速になりますが、ヒープメモリに影響されなくなり、挙動が安定するのでオススメです。
なお、not_analyzedではないstringフィールドには適用できないので注意してください。
Disk-Based Field Data a.k.a. Doc Values | Elastic
"string_field" : { "type" : "string", "index" : "not_analyzed", "doc_values":true }
できるだけエイリアスを使う
アプリからインデックスを参照する際に、インデックス名を直接参照するのではなくエイリアスを設定してそちらを参照することで、変更に強くなります。
一般的に検索仕様の変更などを行う際はインデックス再作成を行う必要があるのですが、エイリアスを利用すればダウンタイム無しで柔軟に対応することができます。
Changing Mapping with Zero Downtime | Elastic
Elasticsearch インデックス・エイリアス – Hello! Elasticsearch. – Medium
Elasticsearch のインデックスを無停止で再構築する - クックパッド開発者ブログ
以下は、test_indexに紐付いていたエイリアスをtest_index_2に切り替える際のコマンドです。アプリからは常にtest_index_aliasを参照するようにしておけば、ダウンタイム無しでアプリから新しいindexを参照できるようになります。
curl -XPOST 'http://localhost:9200/_aliases' -d ' { "actions" : [ {"remove": {"index": "test_index", "alias": "test_index_alias" }}, {"add": {"index": "test_index2", "alias": "test_index_alias" }} ] }'
必要に応じてルーティングを行う
デフォルトではデータがどのシャードに配置されるかはidのハッシュ値をシャード総数で割った値で決定されますが、マッピング時に任意のフィールドを_routingに指定する事で、そのフィールドの値によってシャードの配置を決めることができます。
適切に設定すると検索時の負荷を減らすことができますが、不適切なフィールドを指定するとホットシャードが生まれてしまうので注意して設定してください。自信が無いならデフォルト設定のままで良いです。
対象のフィールドは、store:trueかつindex:not_analyzedである必要があります。また、required:trueでなくてはなりません。
_routing field | Elasticsearch Reference [6.1] | Elastic
{ "mappings" : { "sample_type" : { "_routing" : {"required" : true, "path" : "route_param"}, "properties" : { "route_param" : { "type" : "string", "index" : "not_analyzed", "required" : "true", "store" : true } } } } }
本体の設定
ヒープメモリを適切に設定する
公式に書いてある通り、ヒープメモリは物理メモリの半分までで32GBを超えないようにしましょう。
Heap: Sizing and Swapping | Elasticsearch: The Definitive Guide [2.x] | Elastic
/etc/sysconfig/elasticsearch
ES_HEAP_SIZE:4g
スワップしないように設定
公式で「起動時にメモリを確保し、スワップしないようにする」という設定が推奨されています。
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-configuration.html
起動時のメモリ確保は上に書いたES_HEAP_SIZEの設定でOKなので、後はスワップしないようにする設定を行います。
/etc/elasticsearch/elasticsearch.yml
bootsrap.mlockall:true
環境によって上手くいかない時があるので、設定が効いているかどうかhttp://localhost:9200/_nodes/process?prettyを見て確認しましょう。
上手くいかない時は以下の記事も見てみてください。
ElasticSearchでmlockall:trueが効かない時にチェックすること - サナギわさわさ.json
スプリットブレイン対策を行う
スプリットブレイン対策のために、マスターノードを選ぶ際に必要となる接続ノードの数をノード数/2+1
に設定しておきましょう。例えば6台構成の場合は4になります。
/etc/elasticsearch/elasticsearch.yml
discovery.zen.minimum_master_nodes:4
curlから設定する場合はこっち
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"discovery.zen.minimum_master_nodes": 4} }'
field data cacheに上限を与える
Elasticsearchでは各fieldのデータをメモリに載せることで検索を高速化しているのですが、デフォルトでは無制限にメモリに載るため、OutOfMemoryErrorが発生することがあります。 以下は、使用可能なメモリをHEAP_SIZEの75%に変更する例です。
/etc/elasticsearch/elasticsearch.yml
indices.fielddata.cache.size: 75%
検索高速化
Warmerを登録する
各fieldのデータがメモリに載ることで検索が高速化されるのは上述した通りですが、fieldのデータをメモリに載せる処理はインデックス再構築後1回目のクエリが投げられた時に行われるため、1回目のクエリがかなり重くなることが多いです。
Warmerクエリを設定しておくと、インデックス再構築後に自動でクエリを発行してfield cacheを作成してくれるので便利です。
Warmers | Elasticsearch Reference [6.1] | Elastic
curl -XPUT localhost:9200/testindex/_warmer/warmer_1 -d '{ "query" : { "match_all" : {} }, "aggs" : { "aggs_1" : { "terms" : { "field" : "field" } } } }'
queryとfilterを使い分ける
条件検索はqueryでもfilterでも行えますが、スコア計算を用いない検索条件(完全一致やrangeなど)ならqueryではなくfilterを用いた方が早いです。
Optimizing Elasticsearch Searches | Elastic
filterのキャッシュ設定を使い分ける
filterを使うとフィルタで引っかかったリクエストをデフォルトでキャッシュしてくれます。
ただ全てキャッシュしているとメモリが足りなくなる事が多いので、明らかにキャッシュしない方が良い絞り込み条件に関しては検索の際にキャッシュしないように明示的に設定しましょう。
必要無いフィールドは検索の際に取得しない
言うまでも無いですね。。。
インデクシング高速化
困ったら公式を見ましょう。
Performance Considerations for Elasticsearch Indexing | Elastic
あと以下の記事にほとんどまとまっているので、詳しくはそちらを見た方が良いかもしれません。
Elasticsearchインデクシングパフォーマンスのための考慮事項 - Qiita
bulk APIを使う
大規模データの投入には必ずbulk APIを使いましょう。なお、公式では1つのbulk Importのサイズは5~15MBにすることを推奨しています。
curl -XPOST http://localhost:9200/_bulk --data-binary @databatch.json >/dev/null
コミット頻度の変更
ElasticSearchはNRT(Near Real-Time Search)機能を搭載しており、デフォルトで1秒ごとにソフトコミットが実行され、更新結果が検索に反映されます。
インデクシング時にはこれが結構重いため、更新結果をそこまでリアルタイムに反映する必要がない場合は、ソフトコミットの頻度は30sぐらいに設定した方が早くなります。
curl -XPUT http://localhost:9200/testindex/_settings -d ' { "index" : { "refresh_interval" : "30s" } }'
レプリカシャードを一時的に0に設定
インデクシング中のみレプリカシャードを作らないようにすることで、 データ投入にかかる時間が短くなります。リスクとのトレードオフです。
curl -XPUT http://localhost:9200/testindex/_settings -d ' { "index" : { "number_of_replicas" : 0 } }'
ノード追加・削除
ノード追加・削除時はスプリットブレインが起こりやすいので注意してオペレーションしましょう。また、オペレーション後にdiscovery.zen.minimum_master_nodes
を変更するのを忘れないようにしてください。
シャードの自動配置を停止
ノードの追加・削除処理を行う際に、デフォルトだとシャードの再配置が走って負荷が一気に上がります。処理の前にシャードの自動配置を停止しておきましょう。
Elasticsearchのクラスタにノードを追加するときにやっていること - すずけんメモ
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"cluster.routing.allocation.enable": "none"} }'
ちなみにオペレーション終了時に自動配置を有効に戻す場合は以下です。
curl -XPUT http://localhost:9200/_cluster/settings -d '{ "persistent": {"cluster.routing.allocation.enable": "all"} }'
シャードを手動で再配置
ノード追加の際は以下のオペレーションで問題ありませんが、
- シャードの自動配置を停止
- ノードを追加
- シャードの自動配置を有効に戻す
ノード削除の際は一手順増えます。
- シャードの自動配置を停止
- 削除対象ノードからシャードを移動
- ノードを削除
- シャードの自動配置を有効に戻す
シャードの手動再配置は以下のようにして行えます。面倒ですが1つずつ移動させるのをお勧めします。
curl -XPOST 'localhost:9200/_cluster/reroute' -d '{ "commands" : [ { "move" : { "index" : "test", "shard" : 0, "from_node" : "node1", "to_node" : "node2" } } ] }'
終わり
以上です。冒頭にも書きましたが間違っている点・追加した方が良い点などありましたら教えていただけると助かります!
弊社では現在エンジニアを募集中です!