Apache Arrow を用いた OpenTelemetry Protocol の本番環境での活用

Blog posts are not updated after publication. This post is more than a year old, so its content may be outdated, and some links may be invalid. Cross-verify any information before relying on it.

Apache Arrow を用いた OpenTelemetry Protocol(OTel-Arrow)プロジェクトの OpenTelemetry Collector 向けエクスポーターレシーバーのコンポーネントは、現在 OpenTelemetry Collector-Contrib のリリースに含まれています。 本記事は、ServiceNow Cloud Observability において、社内テレメトリーデータの主要な取り込みパスとして OTel-Arrow コンポーネントを使った OpenTelemetry Collector をデプロイした経験に関するケーススタディです。

F5, Inc. が OpenTelemetry プロジェクトに最初のコントリビューションを行って以来、F5 を含むコミュニティメンバーは、コストの高いネットワークリンクを介して大量の OpenTelemetry データを転送するための、信頼性と性能に優れたソリューションへ OTel-Arrow エクスポーターおよびレシーバーコンポーネントを発展させるべく協力してきました。 これらのコンポーネントを社内テレメトリーに使ったところ、非圧縮サイズの 15 分の 1 から 30 分の 1 になる圧縮効果を観測しています。 OpenTelemetry Protocol(「OTLP」)と Apache Arrow を用いた OpenTelemetry Protocol を、同様に構成したトレースパイプラインで並行比較したところ、圧縮効果が 30% 改善することを確認しました。 この調査は特にトレースデータに焦点を当てていますが、ログとメトリクスのシグナルでも本番環境で同様の結果を観測しており、OTel-Arrow ユーザーは同様のパイプライン構成において OTLP と比較して 50〜70% の改善を期待できます。

これまでの実験結果が本番環境で検証されたことで、Apache Arrow を用いた OpenTelemetry Protocol のエクスポーターおよびレシーバーは一般に利用できる状態にあると考えています。

エクスポーターとレシーバーのコンポーネント

Apache Arrow は、OpenTelemetry と組み合わせる技術として優れています。 両プロジェクトには多くの共通点があるからです。 OpenTelemetry と同様に、Apache Arrow は共有データ仕様、トランスポートプロトコル、クロス言語 API 仕様、そして多数のランタイム環境向けにコミュニティが構築した SDK を備えています。

カラム指向データの処理では、Apache Arrow は今や業界標準であり、相互運用性とパフォーマンスの両面から広く利用されています。 ここでは、この技術を使って OpenTelemetry Collector 間の圧縮ブリッジを構築する方法を説明します。

圧縮ブリッジは、「エクスポーター」と「レシーバー」と呼ぶ 2 つの OpenTelemetry Collector、あるいはロードバランサー配下の 2 つの Collector プールで構成されます。

OTEP 0156 設計ドキュメントで説明されているように、エクスポーターは任意の OpenTelemetry データを Arrow レコードバッチに変換します。 Arrow レコードバッチは、標準化されたレイアウトを持つメモリブロックです。 データをコピーせずに、アドレス空間や仮想プロセス境界を越えて交換できます。 この「ゼロコピー」アプローチにより、Arrow レコードバッチを扱える任意の言語でコンパイルされた外部関数やプロセッサー固有の実行「カーネル」を Arrow から呼び出せます。

標準的な OpenTelemetry Protocol データは、リソース、スコープ、および個々のテレメトリー項目の階層構造として表現されています。 スパン、ログ、メトリクスをエンコードする前に、一意なリソース値とスコープ値が識別され、重複排除されたうえで、OTel-Arrow レコードバッチ内の個別の構造としてエンコードされます。 その際、データの局所性を高めるための項目のソートなど、ほかの最適化も行います。

Apache Arrow プロジェクトはプロセス間通信(IPC)のためのプロトコルを規定しています。 Arrow IPC は「分離された」プロトコルであり、一連の Arrow レコードバッチを送信する特定のトランスポート方法に縛られていません。 Apache Arrow には Arrow Flight という独自の設計思想を持つ RPC フレームワークがありますが、私たちの用途では、Arrow IPC を gRPC ストリーム内に埋め込むことで、OpenTelemetry Collector の既存の gRPC 設定、サービス、認可エクステンション、および計装を活用することが自然でした。 この設計により、高度に最適化された Arrow IPC 関数を利用して繰り返し現れるレコードバッチを圧縮およびエンコードしつつ、OTLP/gRPC サービス設定とのドロップイン互換性を提供できます。

OTel-Arrow レシーバーはエンコーダーで行われた変換を逆変換するロジックを適用し、元のデータと等価なデータを再構築して、OpenTelemetry Collector パイプラインの次のコンポーネントに渡します。 将来的には、SDK からプロセッサーに至るまで OpenTelemetry 全体で Arrow をエンドツーエンドで使うことで多くの利点が得られると予想していますが、そのトピックは今後の投稿に譲ります。

パフォーマンスとスケーラビリティ

ユーザーやサービスプロバイダーは、OTel-Arrow エクスポーターおよびレシーバーコンポーネントを、OpenTelemetry Collector のコア OTLP gRPC エクスポーターおよびレシーバーコンポーネントのドロップインの置き換えとして使うことで恩恵を受けられます。 OTLP/gRPC、大きなバッチサイズ、Zstd 圧縮で達成できる最良の結果と比べても、ネットワーク帯域幅を 30〜70% 削減できると期待できます。

高い圧縮効果に価値があり、ネットワークコストがオブザーバビリティの総コストの大きな要因になることは理解していますが、ユーザーはテレメトリー収集インフラストラクチャ自体の信頼性、スケーラビリティ、およびオブザーバビリティも求めています。

私たちは ServiceNow Cloud Observability の本番環境において、社内テレメトリーを収集する主要エージェントとして、OTel-Arrow コンポーネントを使った OpenTelemetry Collector を 1 年以上運用しており、そこで学んだことを OTel-Arrow コンポーネントと OpenTelemetry Collector 自体の改善に反映してきました。

ここでは、OTel-Arrow コンポーネントを使った OpenTelemetry Collector をどのようにチューニングし、信頼性が高くスケーラブルなテレメトリー配信を実現しているかを簡潔に説明します。

OTel-Arrow エクスポーター

OTel-Arrow エクスポーターとレシーバーのペアは、コア OTLP/gRPC エクスポーターとレシーバーのペアと同等の機能を提供するように設計されているため、ユーザーはこれらのコンポーネントを切り替えられます。 OTel-Arrow コンポーネントは元々これらのコンポーネントから派生しており、統合サービス内で OTel-Arrow と並行して OTLP/gRPC リクエストもサポートしています。

OTel-Arrow エクスポーターコンポーネントは、OpenTelemetry トレース、ログ、メトリクスのバッチを運ぶ同期エクスポート呼び出しを起点に、OpenTelemetry データを変換します。 エクスポートコンテキストには、期限、トレースコンテキスト、リクエストごとのヘッダーなどの標準的な gRPC メタデータが含まれ、これらのコンテキストはすべて OTel-Arrow ストリームを通過します。 リクエストがストリームにディスパッチされた後、呼び出し元はレスポンスが返るか期限が切れるのを待ち、その後戻ります。

各 OTel-Arrow ストリームは、スキーマ、辞書、および後続のストリームリクエストから参照できる関連データを含む内部状態を維持します。 ストリームリクエストは、エクスポーターとレシーバーの両方で順次エンコードおよびデコードされるため、正しい状態を維持できます。 ただし、そのぶん各ストリームが処理できるデータ量には限りがあります。 エクスポーターストリームの数を増やすとスループットは向上しますが、各ストリームにはオーバーヘッドがあるため、一般に、圧縮効率の点ではストリーム数が少ないほうが有利です。

OTel-Arrow ストリームは gRPC ストリームであり、HTTP/2 ストリームにマッピングされます。 ストリームの存続期間は、中間ロードバランサーとのネゴシエーションで決まる制限を含む多くの要因によって決まります。 OTel-Arrow エクスポーターは最大ストリーム存続期間の設定をサポートしており、一定間隔でストリームを自動的に再起動します。 圧縮はストリームの存続期間が長いほど向上しますが、効果は次第に小さくなります。

OTel-Arrow エクスポーターは、gRPC ベースのエクスポーター向けに Collector に組み込まれた設定メカニズム(たとえば、エンドポイント、ヘッダー、TLS)を使い、キュー、リトライ、タイムアウト動作を含む Collector の標準エクスポーター機能も使います。 コンポーネントにはエンドポイント設定を含む多くの共通点があるため、ユーザーは既存の OTLP エクスポーターを大幅な再構成なしに OTel-Arrow エクスポーターに置き換えることができます。

設定例を以下に示します。

exporters:
  otelarrow:
    endpoint: collector.local:4317
    tls:
      insecure: true
    arrow:
      num_streams: 1

OTel-Arrow レシーバー

OTel-Arrow レシーバーコンポーネントは、受信した OTel-Arrow ストリームを管理します。 レシーバーは、ストリームごとのリーダーおよびライタースレッドを持つ非同期プロセスを調整します。 パイプライン処理は独立したワーカースレッドを使って実行され、各ストリームのスループットを最大化できます。 レシーバーはいくつかの制限の範囲内で、可能な限り多くの並行作業を処理します。 OTel-Arrow レシーバーのメモリ使用量を制御する 2 つの主要な設定パラメーターがあります。

1 つ目の制限は、スキーマ、辞書、および関連データを含むアクティブストリームが使用する総メモリ量を制御します。 メモリ制限に達すると、レシーバーはリソース枯渇ステータスコードでストリームを終了します。 これにより、予想外に大きいストリームのメモリ要求によってレシーバーがメモリ不足になることを防ぎます。

2 つ目の制限は、パイプラインに入るデータ量を管理し、OTLP と OTel-Arrow の両方のデータパスをカバーします。 この制限に達すると、個別のリクエストまたはストリームはブロックされるか、同時に待機できる数の制限により即座に失敗します。 アドミッション制限は、パイプラインのストールによるレシーバーのメモリ不足を防止します。

OTel-Arrow パイプラインでのメモリ制御にはこれらの制限を使うことを推奨し、標準の memory-limiter プロセッサーの使用は推奨しません。 メモリとロードバランシングに役立つ、gRPC メッセージサイズ制限、ストリームごとの接続制限、キープアライブ制限など、標準 gRPC 設定で利用できるほかの制限もあります。

個々のレシーバーの負荷が大きくなりすぎた場合は、接続数、ストリーム数、および接続の存続期間に制限を設けるために、外部の HTTP/2 ロードバランサーを使うことを推奨します。

設定例を以下に示します。

receivers:
  otelarrow:
    protocols:
      grpc:
        max_recv_msg_size_mib: 16
      arrow:
        memory_limit_mib: 128
    admission:
      request_limit_mib: 128

OTel-Arrow ストリームにおけるバッチ処理とバックプレッシャー

バッチサイズは圧縮パフォーマンスに大きな影響を与えます。 一般に、バッチが大きいほど圧縮効果は高まりますが、コストも伴います。 大きなバッチはより多くのメモリを必要とし、CPU 使用量が増え、パイプラインのネットワークレイテンシーと CPU レイテンシーも増やすため、これらの要因をバランスさせる必要があります。 OpenTelemetry Collector ユーザーは、並行性、キューイング、永続性を含むほかの要因に関連するいくつかのバッチ処理オプションを利用できます。

他のソースからデータを受信し、OTel-Arrow エクスポーターを介してエクスポートするゲートウェイ Collector では、完全に同期的なリクエストパスを推奨します。 呼び出し元は、元のデータを解放する前にパイプラインがリクエストに応答するのを待ちます。 この方法では、個々のストリームが飽和すると、パイプラインがより多くのデータを処理しようとするにつれてレイテンシーとメモリ使用量が上昇します。 この反応は「バックプレッシャー」と呼ばれ、物理的なパイプラインにおける圧力になぞらえたものです。 バックプレッシャーにより、テレメトリープロデューサーはパイプラインに問題が起きていることを知り、別の接続でリトライする機会を得られます。 また、サービスオペレーターはメモリ使用率に基づいてインスタンス数をオートスケールし、負荷の増加に対処する機会を得られます。

詳細については、OTel-Arrow エクスポーターのバッチ処理とバックプレッシャーに関するドキュメントをご覧ください。

パフォーマンス

ServiceNow Cloud Observability では、社内トレースおよびログデータのゲートウェイとして動作する OpenTelemetry Collector を評価しました。 サンプリング後に平均 50 万〜60 万スパン/秒を OTel-Arrow ブリッジ経由で送信し、圧縮データを 250〜300 MB/秒の範囲でエクスポートする社内トレースパイプラインを使って、一連の実験を実施しました。 OTel-Arrow エクスポーターとレシーバーの組み込み OpenTelemetry 計装を使用して、圧縮、失敗率、中央値レイテンシーの観点からパイプラインのパフォーマンスを定量化しました。

実験のセットアップを以下の図に示します。 図では、データが左から右に流れる様子が描かれており、OpenTelemetry で計装されたアプリケーションが、標準の OTLP over gRPC とラウンドロビンロードバランシングを使うゲートウェイ Collector 群にテレメトリーを送信しています。 ゲートウェイ Collector は、上述の concurrent batch プロセッサーを含むさまざまなプロセッサーを適用した後、データを OTel-Arrow エクスポーターに渡します。

ゲートウェイ Collector のプールがロードバランサーへ送信し、そこからバックエンドサービスへ送信します。

ブリッジの反対側では、Envoy ロードバランサーのプールが TLS を終端し、OTel-Arrow レシーバーを実行する Collector プールへストリームを分散します。 Envoy ではラウンドロビン設定を使用しています。 これは、長寿命のストリームのバランシングにおいて、最小負荷ポリシーよりも性能がよいことがわかったためです。 負荷バランスを改善するために、Envoy と OTel-Arrow レシーバーの HTTP/2 max_concurrent_streams 設定の両方を 1 に設定しています。

パイプラインは完全に同期的であり、送信元の OTel SDK とすべての中間パイプラインステージが、バックエンドから各リクエストへの応答を待ちます。 どちらの Collector プールもリトライするようには設定されていません。 かわりに、OpenTelemetry SDK がリトライするように設定しています。

実験方法

これらの実験は稼働中の本番システムで実施されました。 OTel-Arrow ブリッジは、他の設定済みのプロセッサーやプロプライエタリバックエンドなど多くの変数を含むエンドツーエンドパイプラインの一部です。 そのため、これらの結果は、バルクテレメトリー転送における圧縮性能とコスト優位性の相対的な改善だけを示すことを意図しています。

以下の実験結果における各トライアルは 2 時間です。 測定開始前に、テスト対象構成で少なくとも 1 時間稼働させています。 トライアルが有効とみなされるためには、平均成功率が 99.95% を超え、中央値パイプラインレイテンシーが 3 秒未満でなければなりません。 テストした構成では、ゲートウェイ Collector インスタンスのプールが約 35,000 スパン/秒を受信し、サンプリング後に vCPU あたり約 7,000 スパン/秒をエクスポートします。 ゲートウェイ Collector は、CPU 使用率 60% を目標とする Kubernetes の水平ポッドオートスケーリングポリシーによって管理されています。

OTel-Arrow コンポーネントは、OTLP と OTel-Arrow の両方のプロトコルについて、otelcol_exporter_sent、otelcol_exporter_sent_wire、otelcol_receiver_recv、および otelcol_receiver_recv_wire メトリクスを使用して、圧縮済みおよび非圧縮のバイト数を一貫して報告します。 実験結果は、ServiceNow Cloud Observability のメトリクスクエリを使用した時系列クエリの結果として示します。 これらのメトリクスには扱いやすい形で gRPC メソッド名のタグが付いているため、本番環境での圧縮パフォーマンスのモニタリングが容易です。

例として、エクスポーターで観測された削減係数は、以下のクエリを使用して各エクスポートメソッドについて算出されます。

with
    uncompressed = metric otelcol_exporter_sent
        | rate
        | group_by ["method"], sum;
    compressed = metric otelcol_exporter_sent_wire
        | rate
        | group_by ["method"], sum;
join uncompressed / compressed

圧縮に関する数値は圧縮比ではなく削減係数で報告しているため、値が大きいほどパフォーマンスが向上していることを意味します。 圧縮比は削減係数の逆数として計算できます。 たとえば、削減係数 20 は結果が元の 20 分の 1 のサイズになることを意味し、圧縮比 5% に相当します。

OpenTelemetry Collector および OTel-Arrow コンポーネントのバージョン 0.105.0 をテストしました。

実験:ストリーム存続期間の関数としての圧縮

この実験では、OTel-Arrow エクスポーターの最大ストリーム存続期間を変化させました。 仮説は、初期の調査結果で報告したように、ストリームが長いほどデータ量が多くなり、圧縮の機会が増えるため、圧縮が改善されるというものです。 この実験では、4000〜5000 スパンのバッチが 250〜300KiB の範囲のサイズに圧縮されます。

3.75 秒から 4 分までの 5 つの最大ストリーム存続期間の値をテストし、ストリーム存続期間と圧縮の間に期待される関係を確認しました。 4 分を超えるストリーム存続期間はテストしていません。 これは、テスト対象の OTel-Arrow レシーバーにストリームの存続期間を 5 分に制限する gRPC キープアライブ設定があるためです。

ストリーム存続期間エクスポーター削減係数
3.75s15.6
15s16.3
1m16.9
2m17.2
4m17.1

OTLP プロトコルを通過する同様のデータの平均削減係数は 12.0 です。

OTel-Arrow エクスポーターが観測する圧縮削減係数は、通常レシーバーが観測するものよりも 1% 高くなります。 これは、両者が同じ圧縮データを観測する一方で、OTLP のリソースおよびスコープ値の階層構造内での重複排除により、ブリッジの各側で非圧縮サイズが異なるためです。 たとえば、1 分の存続期間テストでエクスポーターがデータを 16.9 倍に削減した場合、レシーバー側で見た削減係数は、非圧縮サイズの変化により 16.8 にとどまりました。

実験:バッチサイズの関数としての圧縮

別の実験では、上述の concurrent batch プロセッサーで使用されるバッチサイズパラメーターを変化させました。 バッチサイズが大きいほど圧縮が改善されることはよく知られていますが、同時にメモリ使用量とパイプラインレイテンシーも増加します。 通常、バックエンドサービスにはリクエストサイズの上限があり、バッチサイズを増やすことの利点に上限が設けられます。

OTel-Arrow ブリッジを、同様に構成された OTLP トレースパイプラインと比較しました。 Apache Arrow を用いた OpenTelemetry Protocol は、ネットワーク帯域幅の使用量を約 30% 削減することを確認しました。

バッチサイズパラメーター(最小、最大スパン数)OTel-Arrow エクスポーター平均削減係数OTLP エクスポーター平均削減係数
1000–125016.411.9
2000–250017.212.0
4000–500017.712.2

実験:OTLP との差分コスト比較

プロジェクト開始時の仮説の 1 つは、バルクテレメトリーデータを送信するユーザーにとって、圧縮の利点がエンコードに必要な追加コンピューティングリソースのコストを上回るというものでした。 このメトリクスは、テレメトリーをサービスプロバイダーへ送信するユーザーのオブザーバビリティ総コストに影響するため、ブリッジのエクスポーター側で特に重視しています。 ここでは、標準 OTLP データの送信コストと OTel-Arrow データの送信コストを比較します。

OTel-Arrow エクスポーターは対応する OTLP エクスポーターよりもやや多くのメモリを使用しますが、その差はコストの大きな要因にはなりません。 これは、ガベージコレクションなどの他の要因により、2 つのエクスポーターが同様のメモリ量を必要とし、テストされたマシンサイズではメモリコストが CPU コストの 10% にとどまるためです。

2 つのエクスポーター構成で発生したコスト、消費した vCPU/時間、送信したテレメトリーの GiB/時間を比較します。 クラウドコンピューティング環境では、1 vCPU/時間の価格がパブリックインターネットへの 1 GiB のエグレスコストよりも低いことが一般的です。 通常、1 vCPU/時間のコストは 1 GiB のエグレスコストの 4 分の 1 から 2 分の 1 程度と推定されます。

この考え方に従うと、vCPU/時間の差とエクスポート量の差を比較することで、OTel-Arrow ブリッジが経済的に理にかなっていることがわかります。 たとえば、あるトライアルでは、OTel-Arrow エクスポーターは平均 77.0 vCPU を使用し、107 GiB/時間のレートでエクスポートしました。 同様の量の集約データを使用した同様のトライアルでは、OTLP エクスポーターが 53.7 vCPU を使用し、143 GiB/時間をエクスポートしたことが観測されました。 ほとんどのクラウド利用契約では、23 vCPU/時間のコストは 36 GiB/時間のコストよりも大幅に低いため、OTel-Arrow ブリッジを採用することでテレメトリー転送の総コストが削減されます。

まとめ

私たちは、さまざまな OTel-Arrow エクスポーター構成のパフォーマンスを報告しました。 いずれのケースにおいても、信頼性やスケーラビリティを犠牲にすることなく、OpenTelemetry OTLP エンコーディングと比較して大幅な圧縮効果が得られることを示しています。 ServiceNow Cloud Observability の社内トレースおよびログデータを使うと、OTel-Arrow は非圧縮データを 15 分の 1 から 30 分の 1 まで小さくし、一般に OTLP プロトコルよりも 30〜50% 少ないネットワーク帯域幅を実現しています。

これは Apache Arrow を用いた OpenTelemetry Protocol プロジェクトの始まりにすぎません。 Apache Arrow レコードバッチ処理の利点を、OpenTelemetry Collector パイプラインのより深い段階まで広げることを期待しています。 OpenTelemetry SDK がこのフォーマットを直接生成し、テレメトリープロセッサーを Apache Arrow ライブラリにもとづいて実装できる、全体コストを大幅に下げるエンドツーエンドの Apache Arrow を用いた OpenTelemetry Protocol パイプラインの実現を期待しています。

それだけでなく、OTel-Arrow ブリッジが現在のテレメトリー総コストを削減できることを示しました。 Apache Arrow を用いた OpenTelemetry Protocol は本番環境で使用できます。 ユーザーおよびテレメトリーシステムの運用者には、低コストなテレメトリー転送のために Apache Arrow を用いた OpenTelemetry Protocol コンポーネントの採用をお勧めします。 この取り組みに関心があり、OpenTelemetry と Apache Arrow のさらなる統合を望む場合は、ぜひコミュニティに参加するか、プロジェクトリポジトリにイシューを投稿してください。