Elasticsearchのインデキシングに関するパフォーマンス検討

Posted by johtani on Tuesday, September 9, 2014

目次

Elasticsearchのインデキシングに関するパフォーマンス検討

原文:performance considerations for elasticsearch indexing

Elasticsearchユーザは様々な楽しいユースケースを持っています。小さなログを追加することから、Webスケールの大きなドキュメントの集合をインデキシングするようなことまでです。また、インデキシングのスループットを最大化することが重要で一般的な目標となります。 「典型的な」アプリケーションに対して良いデフォルト値を設定するようにしていますが、次のちょっとした簡単なベストプラクティスによってインデキシングのパフォーマンスをすぐに改善することができます。それらについて記述します。

第一に、制御できないならば、巨大なJavaヒープを使用しない:必要なサイズ(マシンの持つRAMの半分以下)のheapだけを設定しましょう。Elasticsearchの利用方法のために必要な全体量を設定します。これは、OSにIOキャッシュを制御するためのRAMを残すことを意味します。OSがjavaプロセスをスワップアウトしていないことも確認しましょう。

最新バージョン(現時点では1.3.2)のElasticsearchにアップグレードしましょう:多数のインデキシングに関連する問題点が最新リリースで修正されています。

詳細に入る前に警告:ここで述べるすべての情報は現時点での最新(1.3.2)の情報です。しかし、Elasticsearchの更新は日々行われています。この情報をあなたが見た時点では最新ではなく、正確ではなくなっているかもしれません。自信がない場合はユーザメーリングリストで質問してください。

クラスタのインデキシングスループットをチューニングする場合、Marvelは非常に有用なツールです:ここで述べている各設定を継続的に試し、変更の影響がクラスタの挙動をどのように変更されたかを簡単に可視化することが可能です。

クライアントサイド

bulk APIを常に使いましょう。1リクエストで複数のドキュメントをインデキシングでき、各バルクリクエストで送るのに良いドキュメント数を試しましょう。最適なサイズは多くの要因に依存しますが、最適サイズからずれるならば多すぎるよりも少なすぎる方が良いでしょう。クライアントサイドのスレッドで並列にbulkリクエストを使うか、個別の非同期リクエストを使ってください。

インデキシングが遅いと結論付ける前に、クラスタのハードウェアの性能を引き出せているかを確認して下さい:すべてのノードでCPUやIOが溢れていないかを確認するためにiostattoppsといったツールを使いましょう。もし、溢れていなければ、より多くの並列なリクエストが必要です。しかし、javaクライアントからのEsRejectedExecutionExceptionや、RESTリクエストのHTTPレスポンスとしてTOO_MANY_REQUESTS (429)が返ってきた場合は並列リクエストを多く送りすぎています。もしMarvelを利用しているなら、Node Statistics DashboardTHREAD POOLS - BULKにリジェクトされた数が表示されます。bulkスレッドプールサイズ(デフォルト値はコア数)を増やすのは得策ではありません。インデキシングスループットを減少させるでしょう。クライアントサイドの並列度を下げるか、ノードを増やすのが良い選択です。

ここでは、1シャードに対してインデキシングスループットを最大化する設定に注目します。1つのLuceneインデックスのドキュメントの容量を測定するために、単一ノード(単一シャード、レプリカなし)で最初にテストをして最適化し、クラスタ全体にスケールする前にチューニングを繰り返します。これはまた、インデキシングスループットの要件を見つけるために、クラスタ全体にどのくらいのノードが必要かをラフに見積もるためのベースラインを与えてくれます。

単一シャードが十分機能したら、Elasticsearchのスケーラビリティの最大の利点や、クラスタでの複数ノードによるレプリカ数やシャード数の増加の利点が得られます。

結論を導き出す前に、ある程度の時間(60分)くらいクラスタ全体の性能を計測しましょう。このテストは、巨大なマージ、GCサイクル、シャードの移動、OSのIOキャッシュ、予期しないスワップの可能性などのイベントのライフサイクルをカバーできます。

ストレージデバイス

当然ながらインデックスを保存するストレージデバイスはインデキシングの性能に多大な影響を及ぼします:

  • SSDを利用する:これらは最も速いHDDよりも速いです。ランダムアクセスのための消費電力が低いだけでなく、シーケンシャルIOアクセスも高いです。また、同時に発生するインデキシング、マージや検索のための並列的なIOも高速です。
  • インデックスをリモートマウントされたファイルシステム(例:NFSSMB/CIFS)上に配置しない:代わりにローカルストレージを使う
  • 仮想化されたストレージ(AmazonのElastic Block Storageなど)に注意:仮想化されたストレージはElasticsearchで十分に動作します。また、十分早く簡単に用意できることから魅力的です。しかし、残念なことに、ローカルストレージと比較すると本質的に遅いです。最近の非公式なテストでは、最高の性能を持つプロビジョニングされたIOPSのSSDオプションのEBSでさえ、ローカルインスタンスにあるSSDよりも遅いです。ローカルインスタンスにあるSSDは物理マシン上のすべての仮想マシンから共有されてアクセスされます。もし他の仮想マシンが急にIOが集中した場合に不可解なスローダウンとなることがあることを覚えておいてください。
  • 複数のSSDを複数のpath.dataディレクトリにインデックスをストライピング(RAID0のように):2つは同様で、ファイルブロックレベルでストライピングする代わりに、個別にインデックスファイルレベルでElasticsearchの"stripes"となります。これらのアプローチは、いづれかのSSDの故障によりインデックスが壊れるという、1シャードが故障する(IO性能を高速化することとトレードオフ)というリスクを増加させることに注意してください。これは、一般的に行うのに良いトレードオフです:単一シャードで最大のパフォーマンスを最適化し、異なるノード間でレプリカを追加すると、ノードの故障への冗長化ができます。また、snapshotやrestoreを使って保険のためにインデックスのバックアップを取ることもできます。

セグメントとマージ

新しくインデキシングされたドキュメントは最初にLuceneのIndexWriterによってRAMに保存されます。RAMバッファがいっぱいになった時もしくは、Elasticsearchがflushもしくはrefreshを実行した時など定期的にこれらのドキュメントはディスクに新しいセグメントとして書き込まれます。最後に、セグメントが多くなった時に、Merge PolicyとSuchedulerによってそれらがマージされます。このプロセスは連続的に生じます:マージされたセグメントはより大きなセグメントとなり、小さなマージが幾つか実行され、また、大きなセグメントにマージされます。これらがどのように動作するかをわかりやすく可視化したブログはこちらです。

マージ、特に大きなマージは非常に時間がかかります。これは、通常は問題ありません。そのようなマージはレアで全体のインデックスのコストと比べればささいなものです。しかし、マージすることがインデキシングについていけない場合、インデックスに非常に多くのセグメントがあるような深刻な問題を防ぐために、Elasticsearchはやってくるインデキシングリクエストを単一スレッド(1.2以降)に制限します。

もし、INFOレベルのログメッセージにnow throttling indexingと表示されていたり、Marvelでのセグメント数が増加しているを見た場合、マージが遅れているとわかります。MarvelはIndex Statistics dashboardMANAGEMENT EXTENDEDの部分にセグメント数をプロットしており、それは、非常にゆっくりと指数対数的に増加しており、大きなマージが終了したところがのこぎりの歯のような形で見て取れます。

セグメント数

なぜマージが遅れるのでしょう?デフォルトでElasticsearchはすべてのマージの書き込みのバイト数をわずか20MB/secに制限しています。スピニングディスク(HDD)に対して、これはマージによって典型的なドライブのIOキャパシティを飽和させず、並列に検索を十分に実行させることを保証します。しかし、もし、インデキシング中に検索をしない場合や、検索性能がインデキシングのスループットよりも重要でない場合、インデックスの保存にSSDを使用している場合などは、index.store.throttle.typenoneを設定して、マージの速度制限を無効化するべきです(詳細はこちらをご覧ください)。なおバージョン1.2以前には期待以上のマージIO制限の発生といったバグが存在します。アップグレードを!

もし、不幸にもスピニングディスク(それはSSDと同等の並列なIOを扱えません)をまだ使っている場合、index.merge.scheduler.max_thread_count1を設定しなければなりません。そうでない場合は、(SSDを支持する)デフォルト値が多くのマージを同時に実行させるでしょう。

活発に更新が行われているインデックスでoptimizeを実行しないでください。それは、非常にコストの高い操作(すべてのセグメントをマージ)です。しかし、もし、インデックスにドキュメントを追加が終わった直後はオプティマイズのタイミングとしては良いタイミングです。それは、検索時のリソースを減らすからです。例えば、時間ベースのインデックスを持っており、新しいインデックスに日々のログを追加している場合、過去の日付のインデックスをオプティマイズするのは良い考えです。特に、ノードが多くの日付のインデックスを持っている場合です。

更にチューニングするための設定:

  • 実際に必要のないフィールドをオフにする。例えば_allフィールドをオフ。また、保持したいフィールドでは、indexedstoredかを検討する
  • もし、_sourceフィールドをオフにしたくなるかもしれないが、インデキシングコストは小さい(保存するだけで、インデキシングしない)、また、それは、将来の更新や、前のインデックスを再インデキシングするために非常に価値があり、それはディスク使用率の懸念事項がない限り、オフにする価値はあまりない。それは、ディスクが比較的安価であるので価値がない。
  • もし、インデックスされたドキュメントの検索までの遅延を許容できるなら、index.refresh_interval30sに増やすか、-1を設定して、オフにする。これは、巨大なセグメントをフラッシュし、マージのプレッシャーを減らすことができる。
  • Elasticsearch 1.3.2(稀に、フラッシュ時に過度のRAMを使用するという問題修正した)にアップグレードすることで、index.translog.flush_threshold_sizeをデフォルト(200mb)から1gbに増加し、インデックスファイルのfsyncの頻度を減らす。 MarvelにIndex Statistics dashboardMANAGEMENTにフラッシュの頻度がプロットされている。

インデックスバッファサイズ

巨大なインデックスを構築中はレプリカ数を0にし、あとから、レプリカを有効にする。レプリカが0ということは、データを失った(ステータスがred)時に冗長性がないので、ノードの故障に注意すること。もし、optimize(ドキュメントの追加をすることがないので)を計画するなら、インデキシングが終わったあとで、レプリカを作成する前に実行するのが良いでしょう。レプリカはオプティマイズされたセグメントをコピーするだけになります。詳細はインデックス設定更新を参照。

もし、ノードがヘビーなインデキシングを行っているだけなら、アクティブなシャードのインデキシングバッファに多くてい512MBをindices.memory.index_buffer_sizeに与えてください。(超えてもインデキシングのパフォーマンスは一般的には改善されません。)Elasticsearchはその設定(Javaヒープのパーセンテージもしくはバイト数)を受けて、min_index_buffer_sizeとmax_index_buffer_sizeの値を前提にノードのアクティブシャードに均等に割り当てます;大きな値はLuceneが最初のセグメントをより大きくし、将来的なマージのプレッシャーを減らすことを意味します。

デフォルトは10%で、それで十分です;例えば、もし、5つのアクティブなシャードがノードにあり、ヒープが25GBの場合、各シャードは25GBの10%の1/5=512MB(すでに最大値)を持っています。ヘビーなインデキシングのあと、この設定をデフォルトに下げましょう。検索時のデータ構造のために十分なRAMを確保するために。この設定はまだ動的な設定変更はできません。Issueがここにあります。

インデックスバッファによって現在利用されているバイト数は1.3.0のindices stats APIに追加されています。indices.segments.index_writer_memoryの値を見ることができます。これはMarvelではまだプロットされていませんが、将来のバージョンで追加される予定です。しかし、自分でグラフに追加することもできます。(Marvelはデータは収集しています)

1.4.0では、indices.segments.index_writer_max_memoryとして、indices stats APIにアクティブシャードにどのくらいのRAMバッファが割り当てられているかも表示されます。これらの値はインデックスのシャード事の値として見ることができ、http://host:9200/<indexName>/_stats?level=shardsを使ってみることができます;これは、全シャードに対する合計と、各シャードごとのstatsを返すでしょう。

オートIDの利用もしくは良いIDの利用

もし、ドキュメントのIDがなんでも良い場合、Elasticsearchで採番することができます:これは、(1.2以降)ドキュメントIDをバージョンを探さずに保存できるように最適化され、Elasticsearchの日毎のベンチマークで異なるパフォーマンスを見ることができます。(FastFastUpdateのグラフを比較)

もし、IDを自身が持っていて、自分の支配下でLuceneに対して素早く選ぼうとしているなら、1.3.2にアップグレードしましょう、IDのルックアップがさらにオプティマイズされています。JavaのUUID.randomUUID()はやめましょう。それは、セグメントに対してどのようにIDを割り当てるかという予測やパターン性がないため、最悪のケースでセグメントごとのシークが発生します。

Flake IDsを利用した時のMarvelによるインデックス性能の違い:

flakeIDsPerf

ランダムUUIDを利用した場合:

uuidsPerf

次の1.4.0では、ElasticsearchのID自動採番をUUIDからFlake IDに変更します。

もし、Luceneのローレベル操作がインデックスに対してなにをやっているかについて興味があるなら、lucene.iwをTRACEログレベルで出力できるようにしてみましょう(1.2から利用可能)。これは、多くの出力がありますが、LuceneのIndexWriterレベルで何が起きているかを理解するのに非常に役に立ちます。出力は非常にローレベルです:Marvelがインデックスに何が起きているかをよりリアルタイムにグラフを描画してくれます。

スケールアウト

我々は、単一シャード(Luceneインデックス)性能のチューニングに注目してきました。しかし、一旦それに満足できたならば、Elasticsearchはクラスタ全体にわたってインデキシングや検索を簡単にスケールアウトすることに長けています。シャード数(デフォルトでは5)を増やすのは可能です。それは、マシン全体に対して並列度、巨大なインデックスのサイズ、検索時のレイテンシの低下など得ることができます。また、レプリカを1位上にすることは、ハードウェア故障に対する冗長性を持つことを意味します。

最後に、このドキュメントを見ても問題解決しない場合はコミュニティに参加しましょう。例えば、ElasticsearchのユーザMLに投稿するなど。おそらく、修正すべきエキサイティングなバグがあるでしょう。(パッチも常に歓迎です!)


comments powered by Disqus

See Also by Hugo


Related by prelims-cli