2014年09月3日 エンジニアリング

Elasticsearchインデックス作成におけるパフォーマンス考慮事項

By Michael McCandless

Update November 2, 2015: If you're running Elasticsearch 2.0, check out this updated post about performance considerations for Elasticsearch 2.0 indexing.

小さなログラインドキュメントの追加から、Webスケールコレクションの大きなドキュメントのインデキシングまで、Elasticsearchユーザーの導入事例は多岐にわたります。多くの場合、インデキシングスループットを最大限にすることが重要な課題です。私たちはできるだけ「典型的」なアプリケーションに適するように、一般的なデフォルトを設定するようにしていますが、ここで説明する簡単なベストプラクティスを用いれば、インデキシングのパフォーマンスを向上させることができます。

まず、できれば巨大なJavaヒープを使用しないでください。Elasticsearchの使用全体の最大ワーキングセットサイズを保持するために、必要なサイズ(理想的にはマシンのRAMの半分以下)のヒープを設定します。これで残りの(うまくいけばかなりの量の)RAMは、OSがIOキャッシュを管理するため使えます。OSが javaプロセスをスワップアウトしないことを確認してください。

最新のElasticsearch (1.3.2 at this time) にアップグレードしましょう。多くのインデックス作成に関連する問題が、最近のリリースで修正されています。

詳細に移る前に、ここの説明はあくまで現時点 (1.3.2) での最新情報ということにご注意ください。Elasticsearchは日々更新されており、この情報をお読みになった時点ではもはや最新ではなく、正確ではなくなっているかもしれません。不明点はWebフォーラム(discuss.elastic.co)でご質問ください。

Marvel は、クラスタのインデキシングスループットを調整する場合に特に有用です。ここで説明する設定を何度か試す間に、各変更がクラスタの挙動にどのように影響したかを簡単に視覚化することができます。

クライアントサイド

1回のリクエストで複数のドキュメントをインデキシング可能な バルクAPIを常に使用してください。1回のバルクリクエストでの送信に適するドキュメント数をいろいろ試してみてください。最適なサイズが何かは多くの要因によって左右されますが、過少な過ぎるではなく、むしろ多過ぎるぐらいのドキュメントで試してみてください。クライアントサイドのスレッドで並列バルクリクエストを使用するか、別々の非同期リクエストを使用してください。

インデキシングが遅すぎると結論付ける前に、クラスタのハードウェアがフルに活用されているかどうか確認してください。iostatやtop、psなどのツールを使って、すべてのノードでCPUやIOが飽和状態になっていないかをチェックしてください。該当しないのであれば、もっと並行リクエストが必要です。ただし、javaクライアントからのEsRejectedExecutionExceptionや、RESTリクエストのHTTPレスポンスとしてTOO_MANY_REQUESTS (429)が返された場合は、並行リクエストが多過ぎることを意味します。 Marvelを使用している場合、 Node Statistics DashboardのTHREAD POOLS - BULKにリジェクトされた数が表示されます。バルクスレッドプールサイズ(デフォルト値はコア数)を増やすのは得策ではなく、インデキシングスループットが低下します。クライアントサイドの並行度を下げるか、ノードを追加することをお勧めします。

ここで述べた設定では、単一のシャードに対してインデックス作成のスループットを最大化することに焦点を当てています。単一のLuceneインデックスのドキュメントにおける能力を測定するために、まずは、単一ノード(単一シャード、レプリカなし)でテストをして最適化し、クラスタ全体に拡大する前に調整を繰り返します。これにより、インデックススループット要件を満たすために、クラスタ全体でいくつのノードが必要かを概算するためのベースラインが得られます。

単一シャードが十分機能すれば、Elasticsearchのスケーラビリティや、レプリカ数やシャード数を増やすことによるクラスタ内の複数ノードをフルに利用することができます。

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

ストレージデバイス

当然ですが、インデックスを保存するストレージデバイスは、インデックス作成のパフォーマンスに大きな影響を与えます。

  • ソリッドステートディスク(SSD)を使用してください。最速のHDDよりもはるかに高速です。ランダムアクセス時の消費電力が低いほか、シーケンシャルIOアクセスが高く、同時インデックス作成、マージ、検索に必要とされる並行IOにも優れています。
  • リモートでマウントされたファイルシステム(例:NFSSMB/CIFS) にインデックスを置かないでください。代わりにマシンのローカルストレージを使用します。
  • AmazonのElastic Block Storageなどの仮想ストレージに注意してください。仮想ストレージはElasticsearchで十分に機能し、早く簡単に設定できるのが魅力的ですが、残念ながら専用のローカルストレージと比較すると、継続的にかなり遅いのです。最近の非公式のテストは、最高の性能を持つプロビジョンド IOPSのSSDオプションのEBS でさえローカルインスタンス接続のSSDよりもかなり遅いという結果でした。ローカルインスタンスにあるSSDは物理マシン上のすべての仮想マシンから共有されてアクセスされます。他の仮想マシンが急にIOが集中した場合、不可解な速度低下が起こり得ることを覚えておいてください。
  • 複数のpath.dataディレクトリを設定するか、またはRAID 0アレイを構成することで、複数のSSDにわたってインデックスをストライピングしてださい 。この2つは似ていますが、Elasticsearchは、ファイルブロックレベルでストライピングするのではなく、個々のインデックスファイルレベルで「ストライプ」します。ただし、どちらの方法でも、いずれかのSSDに障害が起きるとインデックスが壊れるため、(IOパフォーマンス向上と引き換えに)単一シャードの障害リスクは高くなります。最高のパフォーマンスが得られるよう単一シャードを最適化し、異なるノードに渡ってレプリカを追加すると、ノード障害に対する冗長性が得られるわけで、これは通常は適切なトレードオフと言えます。また、さらに保険としてスナップショットと復元を使用して、インデックスのバックアップを取っておくこともできます。

セグメントとマージ

新しくインデキシングされたドキュメントは、まずLucene IndexWriterによってRAMに保存されます。 RAMバッファがいっぱいになるか、またはElasticsearchによってフラッシュまたはリフレッシュがトリガされると、これらのドキュメントは、ディスクに新しいセグメントとして定期的に書き込まれます。最終的にセグメントが多くなってくると、 マージポリシーとスケジューラに従ってセグメントはマージされます このプロセスは段階的で、セグメントがマージされて大きなセグメントになり、小さなマージが何回か実行されると、大きなセグメントもさらにマージされていきます。この仕組みについては、こちらで分かりやすく可視化されています。

マージ、特に大規模なマージの実行には非常に長い時間がかかります。このようなマージは稀で、全体的に考えれば通常は問題ありません。しかし、マージがインデックス作成に追いつくことができない場合、Elasticsearchは、単一スレッドに入ってくるインデックス作成リクエストを制限して(1.2)インデックスにセグメントが多すぎるという重大な問題を防ぎます。

INFOレベルのログにインデキシングの制限を促すメッセージが表示されたり、 Marvel のセグメント数が急激に増えている場合は、マージが遅れていることを意味します。Marvelは、Index Statistics dashboardのMANAGEMENT EXTENDEDセクションにセグメント数をプロットします。非常にゆっくりと指数対数的に増え、大きなマージの完了を示す山がいくつか表示され、全体的にのこぎりの歯のような形になるはずです。

マージが遅れるのはどうしてでしょう?デフォルトで、Elasticsearchはすべてのマージの書き込みのバイト数をわずか20MB/秒に制限しています。回転ディスクの場合、これによってマージでHDDドライブのIOキャパシティが飽和することもなく、問題なく検索を並列で実行できます。ただし、インデキシング時に検索しない場合や、検索のパフォーマンスよりはインデキシングのスループットの方が重要な場合、またはインデックスがSSDに保存される場合は、index.store.throttle.typeをnoneを設定して、マージの速度制限を無効にする必要があります。詳細は ストア をご覧ください。 なお1.2より前のバージョンには、 指定を超えたマージIO制限が行われる. という重要なバグがあります。アップグレードしてください!

SSDのように並行IO処理できないHDDをお使いの場合は、 index.merge.scheduler.max_thread_count を1に設定します。デフォルト値はSSD用に設定されているため、 そうしないと多すぎるマージが一度に実行されてしまいます。

更新中のインデックスに 最適化を行わないでください。コストの高い操作で、すべてのセグメントがマージされます。一定のインデックスへのドキュメントの追加が完了すると検索に必要なリソースが軽減されるため、そのタイミングで最適化することをお勧めします。例えば、1日分のログが新しいインデックスに追加される時間ベースのインデックスをお使いの場合、過去の日付のインデックスを最適化することをお勧めします。特に、ノードが多くの日付のインデックスを保持する場合に有効です。

さらに調整するための設定:

  • 例えば、 _allフィールドの無効化など、実際には不要なすべてのフィールドをオフにしてマッピングを調整します。残したいフィールドについては、 indexedまたはstored、およびその方法を調整します。
  • _sourceフィールドの無効を検討されるかもしれませんが、インデックスのコストは通常低く(保存のみで、インデックス作成はしない)将来の更新や、以前のインデックスを再インデックス化するために非常に価値があります。ディスク空き容量が切迫していない限り(ディスクは比較的安価のため問題ないはずですが)無効にする価値はほとんどありません。
  • 最近インデキシングされたドキュメントの検索にある程度の遅延を許容できるのであれば、index.refresh_interval を30sに増やすか、-1に設定して完全に無効にします。これにより、大きなセグメントをフラッシュして、以降のマージに余裕を持たせることができます。
  • 低頻度のフラッシュ時に過度のRAMを使用するという 問題修正 した、Elasticsearch 1.3.2 にアップグレードされた場合、index.translog.flush_threshold_size をデフォルト(現在200メガバイト)から1ギガバイトに上げてインデックスファイルのfsyncの頻度を減らします。
    Marvelは、 Index Statistics dashboardのMANAGEMENTセクションでフラッシュ率をプロットします。
  • 最初の大きなインデックスの作成中はレプリカ数を0にして、その後レプリカを有効にして追いつくようにします。ただし、レプリカが0の時にノードに障害が発生すると、冗長性がないため、データが消失する(クラスタがレッドになる)ので、これだけは注意してください。 最適化 (もうドキュメントは追加されないので)を予定している場合は、インデキシングが完了した後、レプリカを増やす前に実行するのが良いでしょう。こうすれば、レプリケーションでは最適化されたセグメントをコピーするだけで済みます。詳細はインデックス設定の更新をご覧ください。

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

ノードで大量のインデキシングのみを行っている場合は、 indices.memory.index_buffer_size が、アクティブなシャードごとに最大でも512 MBのインデキシングバッファを確保できるサイズがあることを確認してください。512 MB以上あっても、インデキシングのパフォーマンスは向上しません。Elasticsearchはその設定(Javaヒープの割合または絶対バイトサイズ)を受けて、 min_index_buffer_sizeとmax_index_buffer_size値の対象ノードのアクティブシャードに均等に割り当てます。これより大きな値に設定すると、Luceneが書き込む最初のセグメントが大きくなり、以降のマージに余裕がなくなります。

Tデフォルトは10%で、通常はこれでで十分です。例えば、1つのノードに5つのアクティブなシャードがあり、ヒープが25 GBの場合、各シャードには25 GBの10%の1/5 = 512 MB(すでに最大値)が割り当てられます。大量のインデキシングが終わったら、検索時のデータ構造に十分なRAMを確保するために、デフォルト値(現在は10%)に減らしてください。これはまだ動的設定ではないので注意してください。 Issueはこちら

インデックスバッファによって現在使用されているバイト数は、1.3.0の indices stats API Iに追加されています。これはindices.segments.index_writer_memory値で確認できます。これは現在のところMarvelにプロットされておらず、今後のバージョンで対応される予定です。しかしMarvelはデータを収集しているため、ご自分でチャートを追加することができます。

1.4.0では indices stats API に、indices.segments.index_writer_max_memoryとしてアクティブシャードに割り当てられた正確なRAMバッファ値も表示されます。指定されたインデックスごとのシャードの値を参照するには、 http://host:9200/<indexName>/_stats?level=shardsを使用します。これは全シャードの合計と、各シャードの数値を返します。

オートIDの使用または良いIDの選択

ドキュメントのIDが何でもよい場合は、Elasticsearchが自動的に割り当てることができます。ドキュメントごとにIDとバージョン検索を保存できるように最適化され(1.2以降)、Elasticsearchの毎夜のインデックス作成ベンチマーク のパフォーマンスの違いを見ることができます。(FastとFastUpdateのグラフを比較)

自身のIDを持っている場合、ご自身のコントロール下でLuceneのために高速なものを選ぶなら、1.3.2以降にアップグレードしましょう。 ID検索がさらに最適化されています。JavaのUUID.randomUUID()は避けてください。セグメントに対してどのようにIDを割り当てるかという予測やパターン性がないため、最悪の場合、セグメントごとのシークが発生します。

MarvelFlake ID使用時のインデックス作成レートの違い:

完全にランダムなUUIDを利用した場合:

1.4.0では、Elasticsearchが自動生成するIDをランダムのUUIDからFlake IDに変更します。

Luceneがインデックスで実行するローレベル処理に興味があるなら、lucene.iwトレースログを有効化(1.2で利用可能)を試してみてください。大量のログが生成されますが、LuceneのIndexWriterレベルで何が起きているかを理解するには非常に役立ちます。出力は非常にローレベルです。 Marvelでは、インデックスに何が起こっているかがリアルタイムでグラフィカルに表示されます。

スケールアウト

ここでは単一のシャード(Luceneインデックス)のパフォーマンス調整に焦点を当てました。満足する結果が出たならば、クラスタ全体にわたるインデックス作成や検索を簡単にスケールアウトできるElasticsearchの真価が発揮できます。シャード数(デフォルトで5)を増加して、マシン全体にわたる並行処理、より大きなインデックスサイズ、検索時のレイテンシの低下が実現できます。また、レプリカを1つ上げることで、ハードウェア故障に対する冗長性を持たせることになります。

最後に、それでも問題が解決しない場合は、discuss.elastic.coなどに参加してください。バグ修正が必要かもしれません。パッチはいつでも歓迎します!