エンジニアリング

Elasticsearchスニッフィングのベストプラクティス:機能、シチュエーション、理由、方法

Elasticsearchは、運用分析ダッシュボードから、外出中にテラスのある最寄りのレストランを教えてくれるマップに至るまで、さまざまなツールやアプリの検索エクスペリエンスを支えています。そのようなすべての実装環境で、アプリケーションとクラスターがElasticsearchクライアントを介して接続されています。

クライアントとElasticsearchクラスターの接続の最適化は、エンドユーザーエクスペリエンスに大きな影響を与えます。Elasticsearchクライアントには通常、接続する必要のあるノードのURLが設定されています。接続を最適化する方法は多数ありますが、その1つがスニッフィングです。

この記事では、スニッフィングの仕組み、スニッフィングを使用するべき状況と使用を避けるべき状況を判断する方法をご紹介します。

スニッフィングとは

Elasticsearchは分散型システムです。つまり、インデックスを持つ複数のノードが相互に接続され、クラスターを形成しています。分散型システムが備える、フォールトトレランス以外の大きなメリットの1つが、データが複数のノードで共有されていることにより、巨大な単一のノードの場合よりも、検索速度が大幅に高速化するという点です。

クライアントには通常、Elasticsearchクラスターの1つのノードを指す1つのURLが設定されています。これが最もシンプルな構成ですが、この構成には、すべてのリクエストの送信先が特定のコーディネーションノードに集中するという、大きなデメリットがあります。これにより単一のノードに負荷がかかり、全体的なパフォーマンスに影響が及ぶ可能性があります。

解決策の1つは、リクエストがノード間で均等に分散するように、ノードの静的リストをクライアントに渡すという方法です。

別の解決策として、「スニッフィング」と呼ばれる機能を有効にするという方法があります。

ノードの静的リストの場合、ノードが常に実行されているという保証がありません。たとえば、アップグレードのためにノードを停止したり、新しいノードを追加したりすることを想像してみてください。

スニッフィングを有効にすると、クライアントは_nodes/_all/httpエンドポイントの呼び出しを開始し、クラスター内に存在するすべてのノードと各ノードのIPアドレスが含まれたリストが応答として返ってきます。それを受けて、クライアントで接続プールが更新されて新しいノードがすべて使用され、クラスターの状態とクライアントの接続プールの同期が維持されます。クライアントが全ノードのリストをダウンロードしても、マスター専用ノードが汎用API呼び出しに使用されることはありません。

スニッフィングにより、このような検出に関する問題が解決されます。それなのに、デフォルトでスニッフィングが有効になっていないのはなぜなのでしょう?良い質問です。

スニッフィングを使用するべき状況

スニッフィングは諸刃の剣になりえます。_nodes/_all/httpを呼び出してみると、ノードとそれぞれのエンドポイントのリストが返ってきます。しかし、次のような疑問が浮かびます。

  • Elasticsearchクラスターが独自のネットワーク上にある場合はどうなるか?
  • Elasticsearchクラスターがロードバランサーの背後にある場合はどうなるか?

どちらに対する回答も、簡単に言うと「ネットワークが異なるので、まったく役に立たないIPアドレスが返ってくる」です。

これはDockerで試してみることができます。Elasticsearchインスタンス(1つで十分です)を立ち上げて、ローカルマシンから_nodes/_all/httpを呼び出します。ノードのIPアドレスが、使用中のIPアドレスと異なることに気付くはずです。

以下のコマンドを使用して、Elasticsearchインスタンスをブートします。

docker run \ 
-p 9200:9200 \
-e "discovery.type=single-node" \
docker.elastic.co/elasticsearch/elasticsearch:7.8.0

今度は以下のコマンドでノードのIPアドレスを読み取ります(このスニペットでは応答を読みやすくするためにjqを使用しています)。

curl -s localhost:9200/_nodes/_all/http | jq .nodes[].http.publish_address

最後に、ターミナルに出力されたIPアドレスをコピーして、そのIPアドレスにリクエストを送信してみます。

curl {ip_address}:9200 | jq .

正常な応答が返ってこないことがわかります。

これは、クラスターが別のネットワークに存在する場合にスニッフィングを有効にすると、クライアントの接続プールに新しいノードがすべて追加されることを意味します。これが起こるのは、これらのIPアドレスが間違っていることを把握する手段がなく、いずれかのノードに対するクエリがすべて失敗するためです。

正しいIPアドレスを持つ初期ノードがクラスターステータスに存在しないので、クエリは破棄され、すぐに「No living connections」のエラーが表示されます。

修正方法

この問題を解決するには、Elasticsearchを自身のホストにバインドしつつ別のホストに公開する構成にします。http.publish_hostが、まさにそのための構成オプションです。では、前述のDockerコマンドを新しい構成で実行してみましょう。

docker run \ 
-p 9200:9200 \
-e "discovery.type=single-node" \
-e "http.publish_host=localhost" \
docker.elastic.co/elasticsearch/elasticsearch:7.8.0

ここで以下のコマンドを改めて実行すると、

curl -s localhost:9200/_nodes/_all/http | jq .nodes[].http.publish_address

ターミナルには以下のように表示されます。

"localhost/{ip_address}:9200"

公開ホストを構成すると、オフィシャルクライアント(v7以降)のスマートな機能により、IPではなくホストアドレスが使用されます。

ここでの重要なポイントは、スニッフィングを有効にする前に、インフラについて把握しておく必要があるということです。IPアドレス関連のこの問題には多数の解決策がありますが、確実に効く特効薬はありません。結局はシステム構成に依存する問題だからです。

開発時の設計では通常、Elasticsearchクラスターはクライアントと同じネットワークに配置されていますが、これは現実世界では再現できません。セキュリティ上の問題が生じますし、多くの場合、インフラはもっと複雑だからです。これらのIPアドレスを処理するため、ロードバランサーを構成するという方法があります。または、ElasticがElastic Cloudでやっているように、障害が発生したノードをプロキシに対処させるという方法もあります。この場合、クエリは常にクライアントからプロキシに送信され、プロキシから適切なノードに送信されます。

スニッフィングを避けるべき状況

スニッフィングを有効にすると問題が生じる状況は少なくありません。たとえば、次のようなシチュエーションがあります。

  • クライアントによって認証されているElasticsearchユーザーが、ノードAPIにアクセスするための適切な権限(monitoring_userロール)を持っていない
  • クラウドプロバイダーを利用している

一般的に、クラウドプロバイダーはElasticsearchをプロキシの背後に隠します。これにより、返されるアドレスとホスト名がネットワーク内で意味を為さなくなる可能性があるため、スニッフィング操作が役に立たなくなります。そういったクラウドプロバイダーは通常、顧客に代わって面倒なスニッフィングとプーリングの処理を行うので、顧客側でこれらの機能を有効にする必要はありません。

Elastic Cloudを利用している場合は、オフィシャルクライアントにより、接続プールの処理など大半の操作が内部で暗黙に実行されるので、それらの操作の実行に時間を費やす必要がありません。

すでに触れたように、DockerやKubernetesを利用する場合には別の問題が発生する可能性があります。公開ホストのオプションを設定しない限り、スニッフィングしても意味のない結果しか得られません。

おおまかなガイドラインとして、Elasticsearchがクライアントとは別のネットワークに存在する場合や、ロードバランサーが配置されている場合には、スニッフィングは無効にするべきです。有効にするのであれば、スニッフィングを正確に使用できるように、インフラに何らかの設定を施す必要があります。

スニッフィングの方法

クライアントには、複数のスニッフィング戦略が用意されています。それぞれについて見てみましょう。

起動時にスニッフィング

その名のとおり、このオプションを有効にすると、クライアントは、クライアントの初期化時または初回使用時に1回だけスニッフィングリクエストの実行を試みます。

接続不良時にスニッフィング

このオプションを有効にすると、クライアントは、ノードの障害発生時、つまり接続が切断されたりノードが停止したりした際に、毎回スニッフィングリクエストの実行を試みます。

定期スニッフィング

起動時のスニッフィングと接続不良時のスニッフィングに加えて、ピーク時にクラスターの水平スケーリングが頻繁に行われるシナリオでは、定期的なスニッフィングにメリットがある可能性があります。アプリケーションは、ノードのサブセットを正常に表示することができても、定期的にスニッフィングを実行しなければ、水平スケーリングの一環として追加されているノードを見つけることはできません。

カスタム構成

場合によっては、もっときめ細かくスニッフィング手順を制御する必要が生じる可能性もあります。クライアントには、カスタムスニッフィングエンドポイントを構成したり、スニッフィングロジックを独自のロジックで完全に上書きしたりできる柔軟性が備わっています。

まとめ

スニッフィングを有効にすると、アプリケーションのレジリエンス(回復力)が向上し、変化に適応できるようになります。有効にする前に、採用する最適な解決策を見極められるように、使用しているインフラを把握しておく必要があります。場合によっては、スニッフィングを導入しないのが最適解という可能性もあります。

スニッフィングや接続プールについて悩むよりも、シンプルな接続文字列を使用したいという方は、Elasticsearch Serviceの14日間無料の無料トライアルで、Elastic Cloudをお試しください。