エンジニアリング

Elasticsearchクラスターのシャード数はいくつに設定すべきか?

編集者注:バージョン8.3より、「ヒープメモリ1GBあたり20シャード以下」を目安とする方針は非推奨となりました。この変更に伴い本ブログ記事を更新し、最新の推奨事項を掲載いたしました。

Elasticsearchは非常に多用途なプラットフォームです。さまざまなユースケースをサポートし、データの編成および複製戦略に関して高い柔軟性を提供します。ただし、この柔軟性があるために難しくなることもあります。それは、データをインデックスおよびシャードにどのように整理すれば良いかを事前に判断することです。特にElastic Stackにまだ慣れていない場合にはなおさらです。最適な設定ではない場合でも、開始時に必ずしも問題が発生しないこともありますが、次第にデータ量が増大するとパフォーマンス問題が発生する可能性があります。クラスターが保持しているデータ量が多ければ多いほど、問題を修正することはさらに困難になります。大量のデータの再インデックスが必要になる場合もあります。

パフォーマンス問題に直面しているユーザーに対応すると、その原因がデータのインデックス方法およびクラスター内のシャード数であると判明することも珍しくありません。これは特に、マルチテナントまたは時間ベースのインデックスの使用(あるいは両方)が関係しているときに発生します。このことについてユーザーと個人的に、またはイベントやミーティング、弊社のフォーラムで話す際に最もよく聞く質問は、「シャードはいくつにするべきですか?」、「シャードはどのくらいの大きさにするべきですか?」というものです。

このブログ記事は、これらの質問への回答を1か所で得られるように支援することを目的としており、時間ベースのインデックスを使用するユースケース(ロギング分析またはセキュリティ分析など)に関して実用的なガイドラインを提供します。

シャードとは

本題に入る前に、後のほうのセクションで必要になるいくつかの事実と用語を説明しておきましょう。

Elasticsearchのデータは、複数のインデックスに整理されます。各インデックスは1つ以上のシャードで構成されています。各シャードはLuceneインデックスのインスタンスです。これは、Elasticsearchクラスター内のデータのサブセットに対するクエリをインデックスおよび処理する、自己完結型の検索エンジンと考えることができます。

データがシャードに書き込まれると、ディスク上の新しい不変のLuceneセグメントに定期的にパブリッシュされ、これによってクエリに利用できるようになります。これはリフレッシュと呼ばれます。この仕組みの詳細については、Elasticsearch: the Definitive Guideで説明しています。

セグメント数が増えると、より大きなセグメントに定期的に統合されます。このプロセスはマージと呼ばれます。すべてのセグメントは不変なため、インデックス時には通常、使用されているディスク容量が変動することになります。つまり、マージされた新しいセグメントを作成してから、元のセグメントを消去する必要があるからです。マージは、特にディスクI/Oに関して、リソースに高い負荷がかかります。

シャードとは、Elasticsearchでクラスターにデータを分散するときの単位です。たとえば障害発生後など、データのリバランスを実行する際にElasticsearchでシャードを移動させられる速度は、シャードのサイズと数、およびネットワークとディスクのパフォーマンスに依存します。

ヒント: クラスターの障害復旧の能力に悪影響を及ぼす可能性があるため、あまりにも大きなシャードサイズを設定するのは避けるようにしましょう。シャードサイズに関する決まった上限はありませんが、よく言われているシャードサイズの上限は50GBです。これがさまざまなユースケースで実用可能な大きさと考えられています。

保持期間ごとのインデックス

セグメントは不変なため、Elasticsearchでドキュメントを更新する場合は、まず既存のドキュメントを見つけ、それを削除済みとしてマークし、その後、更新バージョンを追加する必要があります。ドキュメントを削除する場合も、そのドキュメントを見つけて削除済みとしてマークする必要があります。このため、削除済みとしたドキュメントはマージされるまで、ディスクスペースと一部のシステムリソースを引き続き拘束することになり、結果として多くのシステムリソースを消費する場合があります。

Elasticsearchでは、ファイルシステムから直接、効率的にインデックスを完全削除することができます。すべてのレコードを個別に明示的に削除する必要はありません。これはElasticsearchでデータを削除するときの最も効率的な方法です。


ポイント:可能な場合は必ず、時間ベースのインデックスを使用してデータ保持期間を管理するようにします。保持期間に基づいてデータをインデックスにグループ化します。また、時間ベースのインデックスでは、後からプライマリシャードとレプリカの数を簡単に変更することもできます。そのため、次に生成されるインデックスに合わせて変更できます。これによってデータのボリュームと要件の変化に簡単に適応できます。


インデックスとシャードは自由に設定できない?

Elasticsearchでの各インデックスについては、マッピングおよびステート情報がクラスターステートに保存されます。これは迅速なアクセスのためにメモリに格納されます。クラスターに多数のインデックスおよびシャードがあると、特に大規模なマッピングの場合はクラスターステートが大きくなります。一貫性を保証するために単一のスレッドですべての更新を実行してから、クラスター全体にそれらの変更内容を分散する必要があるため、クラスターステートが大きくなると更新の速度が遅くなる可能性があります。


ポイント:インデックスの数を減らし、大規模で広範なマッピングになることを回避するためには、データの提供元に基づいて個別のインデックスに分けるのではなく、同一のインデックスに同様の構造でデータを保存することを検討してください。インデックスとシャードの数、および個別の各インデックスのマッピングサイズ、この両方の良好なバランスを見つけることが重要です。クラスターステートは各ノード(マスターを含む)のヒープにロードされます。ヒープの量はインデックス数、インデックスごとのフィールド数、およびシャード数に直接比例するため、マスターノードのヒープ使用率をモニタリングし、そのサイズが適切であることを確認することも重要です。


各シャードには、メモリに格納しヒープ領域を使用する必要のあるデータがあります。これにはデータ構造が含まれており、シャードレベルで情報を保持するだけでなく、ディスク上のどこにデータが存在しているかを定義するためにセグメントレベルでの情報も保持しています。これらのデータ構造のサイズは固定ではなく、ユースケースによって変わります。

セグメント関連のオーバーヘッドの重要な特徴の1つに、オーバーヘッドはセグメントのサイズに厳密に比例しないというのがあります。つまり、大きなセグメントは小さなセグメントと比較して、データ量ごとのオーバーヘッドが少ないということです。その違いは相当なものになる可能性があります。

ノードごとにできるだけ多く保存できるようにするためには、ヒープ使用率を管理し、できる限りオーバーヘッドを減らすことが重要になります。ノードのヒープ領域が大きければ大きいほど、さらに多くのデータおよびシャードに対応できます。

このようにクラスターの観点から見ると、インデックスとシャードは自由に設定できません。各インデックスおよびシャードにはある程度のリソースオーバーヘッドがあります。


ポイント:小さなシャードは小さなセグメントとなり、結果としてオーバーヘッドが増えます。そのため、平均シャードサイズは最小で数GB、最大で数十GBに保つようにしましょう。時間ベースのデータを使用するケースでは、シャードサイズを20GBから40GBにするのが一般的です。

ポイント:シャードごとのオーバーヘッドはセグメントの数とサイズに依存するため、forcemerge関数を使用して小さなセグメントをより大きなセグメントにマージすれば、オーバーヘッドを削減し、クエリパフォーマンスを向上させることができます。この作業は、インデックスにこれ以上データを追加することがない場合に実行することが理想的です。これは負荷の高い作業であるため、オフピークの時間帯に実行するようにしてください。

ポイント:ノードに保持できるシャード数は、利用できるヒープ量に比例しますが、Elasticsearchによって強制される固定の上限はありません。経験則では、ノードごとのシャード数は構成したヒープのGBあたり20未満に維持することが良いと言えます。したがって30GBのヒープでは最大600シャードとなりますが、この上限よりも大幅に下回る数にするほうがより適切です。一般的にはそのほうがクラスターを良好な状態に維持できます。(編集者注:8.3リリースで1シャードあたりのヒープ使用量が劇的に減少したことに伴い、本ブログ記事で示す方針を変更します。Elasticsearch 8.3以降のバージョンでは、次の内容に従ってください。)

設定のポイント(最新):データノード上で1インデックスあたり、1フィールドあたり、オーバーヘッドのヒープとして1KBを割り当てる
各マップ済みフィールドの正確なリソース使用量はタイプによって異なるものの、目安として各データノードにもたせる1インデックスあたり、1マップ済みフィールドあたりのヒープオーバーヘッドとして約1KBを割り当てます。Elasticsearchのベースライン使用量に加え、インデックス、検索アグリゲーションといったワークロードにも十分なヒープを割り当てる必要があります。0.5GBの余剰のヒープを割り当てれば、一般的な多くのワークロードをまかなうことができます。ワークロードがかなり軽い場合は減らす、逆にワークロードが高負荷であれば増やすなどの調整もできます。

たとえば1つのデータノードのシャードに1,000個のインデックスがあり、それぞれに4,000個のマップ済みフィールドが含まれる場合は、おおよそ、1,000 × 4,000 × 1KB = 4GBのヒープをフィールドに、さらに0.5GBのヒープをワークロードとその他のオーバーヘッドに使用することになり、最低で4.5GBのヒープサイズが必要です。


シャードサイズがパフォーマンスに与える影響

Elasticsearchでは、各クエリはシャードごとに単一のスレッドで実行されます。ただし、同一のシャードに複数のクエリおよび集約が実行できるのと同様に、複数のシャードを並行して処理することが可能です。

これはつまり、キャッシングが関係しない場合、クエリの最小レイテンシはデータ、クエリのタイプ、およびシャードサイズに依存することになります。多数の小さなシャードへのクエリはシャードあたりの処理速度が速くなりますが、多数のタスクをキューに追加し、順番に処理する必要がある場合は、より少数の大きなシャードへのクエリよりも速いとは限りません。多数の小さいシャードがあると、複数の同時クエリがある場合はクエリのスループットが低下することもあります。


ポイント:クエリパフォーマンスの観点から最大シャードサイズを判断する最良の方法は、現実的なデータおよびクエリを使用してベンチマークを実施することです。ベンチマークは常に、運用環境でノードが処理するのと同様のクエリおよびインデックス負荷を使用して実施します。単一のクエリ向けに最適化することは、誤解を招く結果を導き出す可能性があります。


シャードサイズの管理方法

時間ベースのインデックスを使用する場合、各インデックスは固定された期間に関連付けられており、従来からそのようになっています。非常に一般的なのは日次インデックスであり、短い保持期間のデータまたは日次の大容量のデータを保持するためによく使用されています。これによって保持期間をきめ細かく管理することができ、日次ベースで変化する容量に合わせて簡単に調整できます。より長期の保持期間のデータには、特に日次のボリュームが日次インデックスを必ずしも使用しない場合、シャードサイズを大きくするために週次または月次のインデックスがよく使用されます。これにより、クラスターに保存する必要のあるインデックスおよびシャードの数を長期的に減らすことができます。


ヒント:固定期間をカバーする時間ベースのインデックスを使用する場合は、保持期間と予想されるデータ量に基づいて、各インデックスがカバーする期間を調整し、目標のシャードサイズになるようにします。


データ量が適度に予測可能で変化が遅い場合には、固定された時間間隔をカバーする時間ベースのインデックスが適しています。インデックスレートがすぐに変化する場合、目標とするシャードサイズに均一に維持することは非常に難しくなります。

そのようなタイプのシナリオにうまく対処できるように、Rollover APIおよびShrink APIが導入されています。これらは、特に時間ベースのインデックスに関して、インデックスおよびシャードの管理方法に高い柔軟性を提供します。

RolloverインデックスAPIでは、インデックスに含めるドキュメント数、またはインデックスにドキュメントが書き込まれる最大期間(あるいはその両方)を簡単に指定することができます。それらの基準のうち1つが超過すると、Elasticsearchではダウンタイムなしで書き込まれるようにするために新しいインデックスの作成がトリガーされます。各インデックスが特定の期間をカバーするのではなく、指定値を超えるサイズになると新しいインデックスに切り替えられるようにすることが可能になりました。これにより、すべてのインデックスで均等なシャードサイズをより簡単に実現できるようになります。

データが更新される可能性がある場合にこのAPIを使用すると、イベントのタイムスタンプとそのイベントが保存されているインデックスの間には明確なリンクがなくなります。そのため、各更新の前に検索が必要になることがあり、更新の効率が大幅に低下する可能性があります。


ポイント:時間ベースの不変データがあり、そのボリュームが時間とともに大幅に変化する場合は、目標とする最適なシャードサイズを実現するために、RolloverインデックスAPIを使用して各インデックスがカバーする期間を動的に変更することを検討してください。そうすることで高い柔軟性がもたらされ、ボリュームが予測できない場合に、大きすぎるまたは小さすぎるシャードを回避するのに役立ちます。


ShrinkインデックスAPIでは、既存のインデックスを、より少ないプライマリシャードを使用して新しいインデックスに縮小できます。インデックス時にノードに渡って均一のシャード数にしようするとシャードが小さくなりすぎる場合は、このAPIを使用してプライマリシャード数を減らすことができます(この後シャードにさらにインデックスを含めない場合)。こうすることで、データの長期保存に適した大きいシャードとなります。


ポイント:各インデックスが特定の期間をカバーする必要があり、かつ、多数のノードに渡ってインデックスを分散できるようにする場合は、Shrink APIを使用してプライマリシャード数を減らすことを検討してください(その後シャードにさらにインデックスを含めない場合)。また、このAPIは、最初に設定したシャード数が多すぎる場合に、そのシャード数を減らすためにも使用できます。


まとめ

このブログ記事では、Elasticsearchでの優れたデータ管理方法についてのヒントと実用的なガイドラインを提供しました。さらに詳細についてご関心をお持ちの場合は、「Elasticsearch: the definitive guide」内のスケールアップに向けた設計についてのセクションをお読みください。少し前に作成したガイドですが、読む価値のある内容となっています。

インデックスとシャードに渡ってデータをどのように分散させるかについての判断は、その多くがユースケースの詳細に依存します。また、利用可能なアドバイスをどのように適用すれば良いかの判断が難しい場合もあります。詳細や個人的なアドバイスについてはサブスクリプション(有料)を通じて弊社までお問い合わせいただければ、サポートチームおよびコンサルティングチームがプロジェクトの促進をサポートいたします。ユースケースについてオープンにお話しいただくことが可能な場合は、弊社のコミュニティおよびパブリックフォーラムで支援を受けることもできます。


本ブログ記事の初稿公開日は2017年9月18日です。2022年7月6日に記事の内容を更新いたしました。