Elasticsearchは、2010年のリリース以来、分散型データストアとしてもっとも人気のある検索および分析エンジンのひとつです。オープンソースのツールであり、関連性の高い検索結果を高速でユーザーに提供します。ストリーミングサービスで番組を検索したり、アプリで食料品の宅配を注文するときには、Elasticsearchがそのジャーニーの一部に使用されていることが多いでしょう。

ElasticsearchをRubyアプリケーションの他の部分と関連づけて監視することは、Elasticsearchクラスタへのリクエストに関連するパフォーマンスの問題やエラーを特定、解決するのに役立ちます。

New RelicのRubyエージェントのバージョン8.12.0は、Elasticsearch gemのバージョン7.xおよび8.xの自動インストゥルメンテーションを採用し、お使いのElasticsearchの実装が長期的にどう機能するか、より完全な全体像を提供します。Elasticsearchは自動でインストゥルメンテーションされるため、Elasticsearchクライアントからのリクエストを監視するために必要なことは、Rubyエージェントをバージョン8.12.0以降にアップグレードするだけです。

このブログ記事では、New RelicのチームがElasticsearchでRubyのメタプログラミングをどのようにインストゥルメントしたのか、Rubyエージェントのアップデート方法、そして必要に応じてクエリをキャプチャまたは難読化するためにRubyエージェントをどう設定すればいいのかをご紹介します。

Elasticsearchのメタプログラミングのインストゥルメンテーション

New RelicのRubyエージェントは、メタプログラミングの技法を使用して、自分たちが監視をしたい、また一般に使用されているライブラリの公開メソッドに、New Relicのインストゥルメンテーションコードを挿入します。Elasticsearch gemを含む多くのライブラリは、コールバックやフックの使用を想定して設計されていません。これを機能させるため、メタプログラミングを使用してカスタムインストゥルメンテーションメソッドをこれらのライブラリに追加します。

自動的にデフォルトでModule#prependと呼ばれる手法を使用し、既存のメソッドの継承チェーンにインストゥルメンテーションコードを挿入しますが、これらの2つの手法に矛盾が生じる場合にはalias_methodチェーンを使用します。以下は、チェーンを使用したソースコードの例です。

      to_instrument = if ::Gem::Version.create(::Elasticsearch::VERSION) <
          ::Gem::Version.create("8.0.0")
        ::Elasticsearch::Transport::Client
      else
        ::Elastic::Transport::Client
      end

      to_instrument.class_eval do
        include NewRelic::Agent::Instrumentation::Elasticsearch

        alias_method(:perform_request_without_tracing, :perform_request)
        alias_method(:perform_request, :perform_request_with_tracing)

        def perform_request(*args)
          perform_request_with_tracing(*args) do
            perform_request_without_tracing(*args)
          end
        end

Rubyエージェントは、より柔軟に処理が行えるよう、どちらのインストゥルメンテーションのオプションも提供します。New Relic は、デフォルトで Module#prepend を使用しますが、アプリケーションでNew Relicエージェントと同じメソッドでメソッドチェーンを使用する別のgemが使われている場合、エラーが発生することがあります。そのため、メソッド連鎖 (alias_method) と Module#prepend は、特定の状況でしか一緒に使うことができません。適切に使われなかった場合、これらは無限再帰を生じる可能性があります。SystemStackError 向けのトラブルシューティングガイドでは、インストゥルメンテーションメソッドのひとつで何らかの衝突が生じた場合に、これら2つのオプションについて詳細に説明していますのでご覧ください。

次の画像は、New RelicのElasticsearchライブラリの自動インストゥルメンテーションのデフォルトのオプションを示しています。詳細については、Rubyエージェントドキュメントを参照してください。

New RelicのRubyエージェントは、ランタイムのperform_requestメソッドにインストゥルメンテーションコードを挿入します。Elasticsearch Ruby gemは、このメソッドを通じてすべてのAPIコールを送信するためです。

New RelicのRubyエージェントは、Elasticsearch gemのバージョン7.xおよび8.xをサポートします。

perform_request メソッドには、アップデートや削除など、特定のオペレーションタイプについての情報は含まれません。この問題の解決策として、どのメソッドがperform_requestの呼び出しを担っているかを見るには、 caller_locations を確認すればいいことが分かりました。

caller_locationsが使用されるコードは以下の通りです。

    def nr_operation
      operation_index = caller_locations.index do |line|
        string = line.to_s
        string.include?('lib/elasticsearch/api') && !string.include?(OPERATION)
      end
      return nil unless operation_index

      caller_locations[operation_index].to_s.split('`')[-1].gsub(/\W/, "")
    end

これにより、Elasticsearchに呼び出される特定のオペレーションが提示されるため、スパンに正しいオペレーションタイプを紐づけることができます。

以下の画像は、New RelicでのElasticsearchのスパンのディストリビューティッド(分散)トレーシングを示しています。

Elasticsearchのスパンはdb.instanceという属性を持ち、Elasticsearchのリクエストの送信元のクラスタ名を表示します。これは、特定のクラスタでエラーやパフォーマンスの問題が発生しているかどうかの特定に役立ちます。また、Spanはperform_requestに渡されたクエリパラメータを記録しますが、デフォルトでは機密情報を保護するために難読化されています。

以下の画像は、クラスタ名のdb.instance属性やクエリパラメーターのdb.statementなど、Elasticsearchのアップデートスパンに記録された属性を示しています。

Elasticsearchの設定オプション

New Relicでは、capture_queriesobfuscate_queriesの2つの設定オプションが用意されています。どちらもデフォルトで有効化されています。

elasticsearch.capture_queries が有効化されていると、エージェントはリクエストに送られるクエリの記録を試みます。また、機密情報を隠蔽するために obfuscate_queries を追加しました。これらのオプションは、newrelic.ymlファイルか環境変数を通じて設定ができます。

以下の画像は、New Relicのelasticsearch.capture_queriesの設定を示しています。

次の画像では、New RelicのRubyエージェントのelasticsearch.obfuscate_queriesの設定を説明しています。

Rubyエージェントのアップグレード

Rubyエージェントをアップグレードして、Elasticsearchの監視を開始できます。New RelicのRubyエージェントのバージョン8.xでは、Tiltスレッド(8.2.0)、スレッド(8.7.0)、gRPC(8.10.0)、ログインコンテクストのあるRubyのロガークラス(8.6.0)の新しいインストゥルメンテーションが可能です。また、いくつかのバグ修正と、code-level metrics8.10.0)などの強化も含まれます。

New Relicと連携する

Rubyエージェントは、オープンソースソフトウェアです。私たちのソースコードへの貢献や改善へのご提案をぜひお寄せください。また、コミュニティへのご参加もお待ちしています。今後、何をしたいか、またどんな機能をご要望かについて、ぜひお聞かせください。GitHubでつながりましょう。