エンジニアリング

Real Memory Circuit Breakerでノードの回復性が大きく向上

この記事をお読みの方は「サイトの負荷が増大しても、確実に検索トラフィックを処理できる」、そんな堅牢さをElasticsearchに期待しているはずです。Elasticsearchは当初から分散システムとして、個々のノードの障害に対して回復力を持つよう設計されています。さらにElasticsearch 7.0.0より、新たに性能が大きく向上したクラスター調整アルゴリズムが導入されました。

また、Elasticsearch内の個々のノードも、回復性を考慮した設計になります。ノードに送るリクエスト数が多すぎる場合、またはリクエストが大きすぎる場合、ノードはプッシュバックします。後者の場合、リクエストは_circuit breakers_にアーカイブされます。_circuit breakers_はリクエストを処理するパス中の、特定のポイントに配置されています。たとえば、ネットワークリクエストがノードに入る際や、アグリゲーションを実行する前などです。この基本的な考え方は、リクエストが設定された上限を超えてノードをプッシュしするかどうか事前に推測し、必要ならリクエストを拒否することでOutOfMemoryErrorを回避する、すなわちエラーを防ごうというものです。Elasticsearchには行き交うすべてのリクエストやフィールドデータといった個別の要素の"Circuit Breaker"(回路遮断器、つまりブレーカー)があることに加え、さらに"Parent Circuit Breaker"(ペアレントブレーカー)があり、すべての"Circuit Breaker"を包括的に把握します。ペアレントブレーカーがあることで、Elasticsearchのリクエストが個々のブレーカーの上限内に収まっていても、ブレーカー全体で上限を超える場合にリクエストを拒否することができます。

すべての割り当ての追跡は現実的でないことから、Circuit Breakerは明白に予約されたメモリーのみ追跡しており、正確なメモリーの使用量を事前に推測できないこともあります。つまりCircuit Breakerはベストエフォートメカニズムでしかありません。ノードのオーバーロードに対して一定の回復性を提供しますが、ノードがOutOfMemoryErrorでダウンすることも可能性としてはあり得ることになります。特にヒープメモリーが小さい場合、トラックされないメモリーのオーバーヘッドが比較的大きくなるため、この点が問題になります。

Circuit Breaker改良版の開発とテスト

Circuit Breakerで予約する際、ノードが使用しているメモリー量を正確に把握することができたらどうでしょうか。その場合、各Circuit Breaker間で現在予約された量に基づく推測ではなく、実際の状況に基づいてリクエストを拒否することが可能になります。Elasticsearch 7.0が今回新たに導入するReal Memory Circuit Breakerでできるようになったことが、正にこれです。現在追跡されているメモリーだけを計算する代わりに、現在のメモリー使用量を計測するJVMの機能を使用し、代替版のParent Circuit Breakerを導入しています。いくつかの数字を足すよりはコストがかかりますが、運用上、メモリー使用量の計測は非常に安価です。マイクロベンチマークで観測されたオーバーヘッドは400から900の間でした。 私たちはさまざまな異なる状況下でReal Memory Circuit Breakerの有効性を試すべく、数々の実験を行いました。あるシナリオは、ヒープメモリーをわずか256MBに設定したノードで全文インデックスを実行しベンチマークを測るというものでした。従来のバージョンのElasticsearchではこのワークロードに耐えられず、即時にメモリー不足に陥ります。ところが、Real Memory Circuit Breakerがプッシュバックを実行したことで、新しいバージョンのElasticsearchはこの負荷に耐えることができました。留意点として、こうしたケースでElasticsearchはエラーレスポンスを返すこと、またバックオフと再試行のメカニズムを適切に実装できるかがクライアント依存となることがあります。Elasticがオフィシャルに提供する言語クライアントをすでに使用している場合この作業はとても簡単なので、過度な心配は必要ありません。オフィシャルの.NET、Ruby、Python、Javaクライアントにはすでにこのような再試行ポリシーが実装されており、バルクインデキシングを行うエクステンションも提供されています。

別のテストシナリオでは、16GBのヒープを持つノードで、ありえないほど多数のバケットを意図的に生成させるアグリゲーションを実行しました。従来のバージョンのElasticsearchはこのシナリオでもメモリー不足に陥りましたが、エラーが生じるまで30分近くアグリゲーションを実行しました。一方Real Memory Circuit Breakerを搭載したバージョンでは、1分経ってから、あるいは約20分後に部分的な結果を返すことを許可するといういずれの設定でも、ノードがレスポンスを返しました。こうしてさまざまなテストを行った結果、私たちはReal Memory Circuit Breakerのデフォルト値を、使用できるヒープ全体の95%に設定しました。つまりElasticsearchが使用できるヒープ量は95%までで、それを超えるとReal Memory Circuit Breakerにより「ブレーカーが落ちる」ということになります。

こんな例を考えてみましょう。送られたリクエストは、他のチェックはすべてパスする程度に小さなものですが、ノードの上限を超えるためReal Memory Circuit Breakerが発動します。このノードには128MBのヒープが設定されおり、したがってReal Memory Circuit Breakerの上限である95%は117.5MBです。このリクエストが送られると、ノードは次の詳細情報と共にHTTP 429を返します。

{
  'error': {
    'type': 'circuit_breaking_exception',
    'reason': '[parent] Data too large, data for [<http_request>] would be [123848638/118.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [120182112/114.6mb], new bytes reserved: [3666526/3.4mb]',
    'bytes_wanted':123848638,
    'bytes_limit':123273216,
    'durability':'TRANSIENT'
  },
  'status':429
}

さらにCircuit Breakerは、これが一時的な障害であること、およびクライアントはこのレスポンスをヒントとして使用し、後でリクエストを再試行できることも示唆しています。Circuit Breakerの動作の例外が永続的か、一時的かは、すべてのCircuit Breakerにわたって予約されたメモリー量に基づいて判断されます。Circuit Breakerの動作タイプは持続時間と関連しています。予約されたメモリーの大部分が一時的なメモリー使用量を追跡するCircuit Breakerによる予約であれば、Real Memory Circuit Breakerは一時的な状況として扱い、そうでなければ永続的なものと判断します。

まとめ

新バージョンのElasticsearchノードも、一部のシナリオではメモリー不足を起こす可能性があります。それでもReal Memory Circuit Breakerにより、個々のノードの回復性は著しく向上しました。これは、従来のバージョンがCircuit Breakerが追跡するメモリーのみをカウントしていたのに対し、実際に測定されたメモリー使用量に基づくバックプレッシャーを実行できるようになったためです。テストでは、新バージョンのElasticsearchが以前には不可能だったワークロードを処理できること、また運用環境のクラスターのピーク時に、はるかに高い回復性を発揮することが実証されています。Real Memory Circuit Breakerを搭載した最新の7.0ベータリリースをぜひお試しください。またお気づきの点がございましたら、フィードバックをお寄せください。

本記事のトップ画像はKiran Raja Bahadur SRKによるもので、CC BY-NC-ND 2.0に基づきライセンス提供されています(ソース元)。