エンジニアリング

Elasticsearchのメモリーの管理とトラブルシューティング

こんにちは。ElasticのElasticsearch Serviceクラウドサービスや自動オンボーディングの展開に伴い、Elastic Stackを従来の運用チームだけでなく、データエンジニアやセキュリティチーム、コンサルタントのみなさまにもご利用いただくようになりました。Elasticサポートエンジニアとして、ユーザーのみなさまの経歴やユースケースの多様化を日々実感しています。 

このようなお客様の多様化に伴い、リソース割り当てに関するご質問、特にシャードとヒープの比率の設定やCircuit Breakerの回避に関するご質問が以前より多く寄せられるようになりました。お気持ちはわかります。私も、Elastic Stackを使い始めたときには同じ問題を抱えていました。それがきっかけで、初めてJavaヒープと時系列データベースシャードの管理や、自身のインフラの拡張に挑みました。

Elasticチームに参加したてのころは、ドキュメントだけでなくブログやチュートリアルも用意されており、すぐに馴染めたことに感激したものです。しかし、最初の1か月は、ユーザーのみなさまからチケットでお寄せいただくエラーと、自分の頭の中の知識をすり合わせることがなかなかできませんでした。やがて、他のサポートエンジニアと同じように、報告されるエラーの大部分は割り当ての問題を示す症状であること、そしてお問い合わせいただいたユーザーのみなさまに特定のリンクを7個ほどご案内すれば、リソース割り当ての問題はあっという間に解決することに気がつきました。

以下のセクションでは、サポートエンジニアとして、ユーザーのみなさまによくお送りしている割り当て管理の理論へのリンク、頻繁に見られる症状、およびリソース割り当ての問題を解決するために更新をお願いしている設定についてご紹介します。

理論

ElasticsearchはJavaアプリケーションであるため、システムの物理メモリーから論理メモリー(ヒープ)を割り当てる必要があります。割り当てるメモリーの量は、32GBを上限として物理RAMの半分以下にしてください。一般に、クエリの負荷が高い場合やデータストレージのサイズが大きい場合には、対応としてヒープ使用率を高く設定します。Parent Circuit Breakerのデフォルト設定は95%ですが、継続して85%に達するようになった場合はリソースを拡張することをお勧めします。 

また、Elasticチームが作成した以下の概要記事で詳細をご覧になることを強くお勧めします。

設定

Elasticsearchのデフォルト設定では、ノードロールと総メモリー量に応じてJVMヒープのサイズが自動的に調整されます。ただし、必要に応じて、以下の3通りの方法で設定を直接変更することをお勧めします。

1.ローカルのElasticsearchファイルのうち、config > jvm.optionsファイルを編集する

## JVM設定
################################################################
## 重要:JVMのヒープサイズ
################################################################

#Xmsはヒープ領域全体の初期サイズを指定
#Xmxはヒープ領域全体のサイズ上限を指定
-Xms4g
-Xmx4g

2.docker-composeのElasticsearch環境変数を編集する

version: '2.2'
services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:7.12.0
environment:
- node.name=es01
- cluster.name=es
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.type=single-node
ulimits:
memlock:
soft: -1
hard: -1
ports:
- 9200:9200

3.Elasticsearch Serviceの[Deployment](デプロイ)>[Edit](編集)ビューで変更する。注:スライダーで割り当てるのは物理メモリであり、この割り当て値の約半分がヒープに割り当てられます。

blog-elasticsearch-memory.png

トラブルシューティング

クラスターのパフォーマンスに問題が発生している場合、たいていは以下が原因であると考えられます。

  • 設定の問題:オーバーシャード、ILMポリシーが未設定
  • 発生量:リクエストの頻度または負荷が高すぎる、負荷の大きいクエリまたは書き込みが重複している

以下のcURL/APIリクエストはすべて、Elasticsearch Serviceの[API Console](APIコンソール)、Elasticsearch APIに対するcURL、またはKibanaの[Dev Tools](開発ツール)で実行できます。

オーバーシャード

データインデックスはサブシャードに保存され、サブシャードによりメンテナンス時および検索と書き込みのリクエスト中にヒープが使用されます。シャードのサイズは50GBを上限とし、シャード数は以下の式から算出される上限以下とする必要があります。

shards = sum(nodes.max_heap) * 20

先ほどの物理メモリー8GB、ゾーン2つのElasticsearch Serviceの例では、以下のようになります(合計で2つのノードが割り当てられます)。

#node.max_heap
8GB of physical memory / 2 = 4GB of heap
#sum(nodes.max_heap)
4GB of heap * 2 nodes = 8GB
#シャード数上限
8GB * 20
160

得られた値を_cat/allocationと比較します。

GET /jp/_cat/allocation?v=true&h=shards,node
shards node
41 instance-0000000001
41 instance-0000000000

または、_cluster/healthと比較します。

GET /jp/_cluster/health?filter_path=status,*_shards
{
"status": "green",
"unassigned_shards": 0,
"initializing_shards": 0,
"active_primary_shards": 41,
"relocating_shards": 0,
"active_shards": 82,
"delayed_unassigned_shards": 0
}

以上から、このデプロイのシャード数の推奨上限は160であり、現在のシャード数は82であるとわかります。推奨上限よりも現在のシャード数が多い場合、次の2セクションで示す症状が発生する可能性があります(後述を参照)。

いずれかのシャードでactive_shardsactive_primary_shards以外の値が0より大きい場合は、パフォーマンスの問題を引き起こす設定要素を特定したも同然です。

ほとんどの場合、問題の発生時にはunassigned_shards>0となっています。該当するシャードがプライマリである場合、クラスターはstatus:redとなり、レプリカが存在する場合のみstatus:yellowとなります(そのため、クラスターに問題が生じてもデータを喪失することなく復元できるように、インデックスにレプリカを設定することが重要です)。

ここでは、status:yellowであり、1つのシャードが未割り当ての状態を考えてみましょう。調査を行うために、_cat/shardsで、どのインデックスシャードに問題があるかを調べます。

GET _cat/shards?v=true&s=state
index shard prirep state docs store ip node
logs 0 p STARTED 2 10.1kb 10.42.255.40 instance-0000000001
logs 0 r UNASSIGNED
kibana_sample_data_logs 0 p STARTED 14074 10.6mb 10.42.255.40 instance-0000000001
.kibana_1 0 p STARTED 2261 3.8mb 10.42.255.40 instance-0000000001

これで、問題はシステム以外のインデックスログであり、レプリカシャードが未割り当てであるとわかりました。次は、_cluster/allocation/explainを実行して、この問題の原因を調べます(こぼれ話:これは、サポートがみなさまからお問い合わせをいただいた際に行っている操作そのものです)。

GET _cluster/allocation/explain?pretty&filter_path=index,node_allocation_decisions.node_name,node_allocation_decisions.deciders.*
{ "index": "logs",
"node_allocation_decisions": [{
"node_name": "instance-0000000005",
"deciders": [{
"decider": "data_tier",
"decision": "NO",
"explanation": "node does not match any index setting [index.routing.allocation.include._tier] tier filters [data_hot]"
}]}]}

上記のエラーメッセージで、インデックスライフサイクル管理(ILM)ポリシーの一部であるdata_hotが示されていることから、ILMポリシーが現在のインデックス設定に適合していないとわかります。この場合、エラーの原因は、Hot-Warmノードを指定せずにHot-Warm ILMポリシーを設定したことです(エラーを発生させてみなさんにお見せする必要があったので、自作自演でこのように誤った設定を行いました。なんてことでしょう😂)。

なお、未割り当てシャードが存在しない場合にこのコマンドを実行すると、報告する問題が存在しないため、400エラーとともに[unable to find any unassigned shards to explain](問題の原因となる未割り当てシャードを見つけられませんでした)と表示されます

論理メモリー以外の原因(例:割り当て中のクラスターからのノードの切断などの一時的なネットワークエラー)が考えられる場合は、Elasticの便利な_cluster/rerouteを実行しましょう。

POST /jp/_cluster/reroute

上記リクエストをカスタマイズせずに実行すると、非同期的なバックグラウンドプロセスにより、現在のstate:UNASSIGNEDのシャードすべての割り当てが始まります(私のように、このプロセスの完了を待たずに開発チームに問い合わせることはしないでください。昔、プロセスがすぐに終わると思っていたのでエスカレーションを行ったところ、偶然にもちょうど同じタイミングで、開発チームから何も起きなかったから問題はないという報告をもらったことがあります)。

Circuit Breaker

割り当て済みのヒープが完全に消費されると、クラスターに対するリクエストでタイムアウトまたはエラーが発生する可能性があります。また、多くの場合、クラスターでCircuit Breakerの例外も発生します。Circuit Breakerが起動すると、以下のようなelasticsearch.logイベントが生じます。

Caused by: org.elasticsearch.common.breaker.CircuitBreakingException: [parent] Data too large, data for [] would be [num/numGB], which is larger than the limit of [num/numGB], usages [request=0/0b, fielddata=num/numKB, in_flight_requests=num/numGB, accounting=num/numGB]

原因を探るには、heap.percentを調べます。そのためには、_cat/nodesを確認します。

GET /jp/_cat/nodes?v=true&h=name,node*,heap*
#heap = JVM(ヒープ用に予約済みの論理メモリー)
#ram = 物理メモリー
name node.role heap.current heap.percent heap.max
tiebreaker-0000000002 mv 119.8mb 23 508mb
instance-0000000001 himrst 1.8gb 48 3.9gb
instance-0000000000 himrst 2.8gb 73 3.9gb

または、Kibanaの[Stack Monitoring](スタック監視)を確認します(過去にこの機能を有効にしている場合)。

blog-elasticsearch-memory-2.png

メモリーのCircuit Breakerが発動していることを確認できた場合は、調査の時間を確保するために、ヒープを一時的に増やすことをお勧めします。根本原因を明らかにするには、クラスターのプロキシログまたはelasticsearch.logで、Circuit Breakerの発動直前に発生していたイベントを確認します。具体的には、以下のイベントがないか調べます。

  • 負荷の高いクエリ。特に以下のもの:
    • 高負荷のバケットアグリゲーション
      •  個人的な失敗談として、検索のサイズやバケットのディメンションに基づいてクエリを実行する前に検索で特定のヒープのポートを一時的に割り当てており、設定が10,000,000であったために運用チームにストレスをかけたことがありました。
    • 最適化されていないマッピング
      • 同じく失敗談として、フラット化したデータよりも階層構造のレポートの方が検索のパフォーマンスが良くなると考えていた時期がありました(実際にはそうではありません)。
  • リクエストの量または頻度:主にバッチクエリまたは非同期クエリ

拡張の検討

Circuit Breakerの発動が初めてではない場合、または問題が継続的なものと考えられる場合(たとえば、常に85%に達しているのであれば、リソースの拡張を検討するべきです)は、ヒープの長期的指標となるJVMのメモリー負荷を詳しく調べることをお勧めします。これを調べるには、Elasticsearch Serviceの[Deployment](デプロイ)にアクセスします。

blog-elasticsearch-memory-3.png

または、_nodes/statsの結果から計算することもできます。

GET /jp/_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old
{"nodes": { "node_id": { "jvm": { "mem": { "pools": { "old": {
"max_in_bytes": 532676608,
"peak_max_in_bytes": 532676608,
"peak_used_in_bytes": 104465408,
"used_in_bytes": 104465408
}}}}}}}

JVMのメモリー負荷は以下の式で求められます。

JVM Memory Pressure = used_in_bytes / max_in_bytes

この場合の症状としては、elasticsearch.logでガーベージコレクター(gc)イベントの頻度と実行時間が増すことが挙げられます。

[timestamp_short_interval_from_last][INFO ][o.e.m.j.JvmGcMonitorService] [node_id] [gc][number] overhead, spent [21s] collecting in the last [40s]

このような状況が認められた場合は、クラスターの拡張、またはクラスターに対する要求の削減を検討する必要があります。以下の対応を調査、検討することをお勧めします。

  • ヒープリソース(ヒープまたはノード、ノード数)を増やす
  • シャードを減らす(不要または古いデータを削除する、ILMを使用してデータをWarmまたはColdストレージに移し縮小する、喪失しても問題のないデータについてレプリカを無効にする)

まとめ

おつかれさまでした。Elastic Supportでの経験に基づき、未割り当てシャード、シャードとヒープの不均衡、Circuit Breaker、ガーベージコレクションの頻度増、および割り当てのエラーという、サポートチケットでよくお問い合わせいただく問題を順番にご紹介しました。これらはすべて、コアリソースの割り当て管理に関するお問い合わせで見られる症状です。この記事で、理論と解決手順に対する理解を深めていただければ幸いです。

ただし、問題解決で行き詰まった際には、お気兼ねなくお知らせください。喜んでサポートいたします。Elastic DiscussElasticコミュニティのSlack、コンサルティング、トレーニング、サポートからお問い合わせください。

運用チームのみなさまはもちろん、運用チーム以外のみなさまにも、Elastic Stackのセルフサービス形式のリソース割り当て管理機能をご活用いただけることを願っています。