> Source: https://www.ymotongpoo.com/works/oteps/otep-0156/


# OTEP-0156: OTel Arrowプロトコル仕様

**著者**: Laurent Querel, F5 Inc.

**キーワード**: OTLP、Arrowカラム型フォーマット、帯域幅削減、多変量時系列、ログ、トレース。

**要約**: このOTEPは、メトリクス、ログ、トレースのための**汎用的なカラム型表現**に基づく新しいプロトコルであるOTelArrowプロトコルについて説明します。
このプロトコルは、大量のメトリクス、ログ、トレースのバッチを送信するシナリオにおける効率を大幅に改善します。
さらに、[多変量時系列](#multivariate-time-series)に対してより優れた表現を提供します。
OTelArrowプロトコルはまた、エンドポイントの一方がOTelArrowプロトコルをサポートしていない場合に、[OpenTelemetryプロトコル（OTEP 0035）](0035-opentelemetry-protocol.md)へフォールバックする仕組みもサポートしています。

**リファレンス実装**: [OTel Arrow Adapter](https://github.com/f5/otel-arrow-adapter) Goライブラリは、protobuf仕様を定義し、OTel Arrow Encoder/Decoderを実装しています（主なコントリビューターは[Laurent Querel](https://github.com/lquerel)）。
新しいgRPCエンドポイントを公開し、以前のライブラリを通じてOTel Arrowサポートを提供するために、[実験的なOTel Collector](https://github.com/open-telemetry/otel-arrow-collector)が実装されています（主なコントリビューターは[Joshua MacDonald](https://github.com/jmacd)）。

## 目次

* [はじめに](#introduction)
  * [動機](#motivation)
  * [検証](#validation)
  * [なぜApache Arrowなのか、どう使うのか](#why-apache-arrow-and-how-to-use-it)
  * [統合戦略とフェーズ分け](#integration-strategy-and-phasing)
* [プロトコルの詳細](#protocol-details)
  * [ArrowStreamService](#arrowstreamservice)
  * [OTelエンティティとArrowレコードのマッピング](#mapping-otel-entities-to-arrow-records)
    * [ログのArrowマッピング](#logs-arrow-mapping)
    * [スパンのArrowマッピング](#spans-arrow-mapping)
    * [メトリクスのArrowマッピング](#metrics-arrow-mapping)
* [実装に関する推奨事項](#implementation-recommendations)
  * [プロトコル拡張とフォールバック機構](#protocol-extension-and-fallback-mechanism)
  * [バッチID生成](#batch-id-generation)
  * [スキーマID生成](#schema-id-generation)
  * [トラフィックバランシングの最適化](#traffic-balancing-optimization)
  * [スロットリング](#throttling)
  * [配信保証](#delivery-guarantee)
* [リスクと緩和策](#risks-and-mitigations)
* [トレードオフと緩和策](#trade-offs-and-mitigations)
  * [重複データ](#duplicate-data)
  * [非互換なバックエンド](#incompatible-backends)
  * [小型デバイス／小規模なテレメトリーデータストリーム](#small-devicessmall-telemetry-data-stream)
* [将来のバージョンと相互運用性](#future-versions-and-interoperability)
* [先行技術と代替技術](#prior-art-and-alternatives)
* [未解決の課題](#open-questions)
* [将来の可能性](#future-possibilities)
* [付録A - プロトコルバッファの定義](#appendix-a---protocol-buffer-definitions)
* [用語集](#glossary)
* [謝辞](#acknowledgements)

## はじめに {#introduction}

### 動機 {#motivation}

テレメトリーデータがより広く利用されるようになり、データ量が増加するにつれて、OTLPエコシステムに対する新しい用途や要求が生まれています。
それは、コスト効率、高度なデータ処理、データの最小化です。
このOTEPは、既存のエコシステムとの互換性を維持しながら、これらの要求により良く対応できるようにOTLPプロトコルを改善することを目的としています。

現在、OTLPプロトコルはすべてのOTelエンティティを表現するために「行指向」の形式を使用しています。
この表現は小規模なバッチ（50エントリ未満）に対してはうまく機能しますが、分析データベース業界が示してきたように、*大規模なバッチ*のエンティティの転送や処理には「カラム指向」の表現の方がより最適です。
「行指向」という用語は、データがレコードの並びとして構成され、あるレコードに関連するすべてのデータがメモリ上で隣接して配置される場合に使われます。
「カラム指向」のシステムは、データをフィールドごとに構成し、あるフィールドに関連するすべてのデータをメモリ上で隣接して配置します。
カラム型アプローチの主な利点は次のとおりです。

* **データ圧縮率の向上**（類似したデータの配列は一般的により良く圧縮できます）
* **データ処理の高速化**（下図を参照）
* **シリアライズ・デシリアライズの高速化**（シリアライズ・デシリアライズするオブジェクトが多数のメモリ上オブジェクトではなく少数の配列で済みます）
* **IO効率の向上**（送信するデータが少なくなります）

![row vs column-oriented](img/0156_OTEL%20-%20Row%20vs%20Column.png)

このOTEPは、**Apache Arrowに基づく、メトリクス、ログ、トレースのための汎用的なカラム型表現**によって、[OpenTelemetryプロトコル（OTEP 0035）](0035-opentelemetry-protocol.md)を改善することを提案します。
既存のOpenTelemetryプロトコルと比較して、この互換性のある拡張には次のような改善点があります。

* プロトコルの**帯域幅要件を削減する**こと。
  主な2つの手段は、1) カラム型表現に基づくテレメトリーデータのより良い表現、2) OTLPエンティティのバッチを送信するのに効率的なストリーム指向のgRPCエンドポイント、です。
* **多変量時系列データに対してより最適な表現を提供する**こと。
  多変量時系列は、既存のプロトコルでは現状うまく圧縮されていません（多変量とは、同じ属性とタイムスタンプを共有する関連メトリクスのことです）。
  OTel Arrowプロトコルは、カラム型表現を活用することで、この種のデータに対してはるかに優れた圧縮率を提供します。
* **より高度で効率的なテレメトリーデータ処理機能を提供する**こと。
  データ量の増加、コスト効率、データの最小化には、データの射影、集約、フィルタリングなど、追加のデータ処理機能が必要です。

これらの改善は、前述の要求に対応するだけでなく、OTEP 035で挙げられている[未解決の課題](0035-opentelemetry-protocol.md#open-questions)（すなわちCPU使用率、メモリ圧迫、圧縮の最適化）にも回答するものです。

**この提案は既存のプロトコルを補完するものであることを理解しておくことが重要です。
行指向版は依然として一部のシナリオに適しています。
少量のテレメトリーデータしか生成しないテレメトリーソースは、引き続きそちらを使用すべきです。
スペクトラムの反対側では、大量のテレメトリーデータを生成または集約するソースやCollectorは、この拡張を採用することで、そのデータの転送と処理に関わるリソースを最適化する恩恵を受けられます。
この採用は段階的に行うことができます。**

OTel Arrowプロトコルの仕様を詳しく説明する前に、以下の2つのセクションでは、1) 一連のベンチマークに基づくカラム型アプローチの価値の検証、2) OTLPにおけるカラム型サポートの基盤としてApache Arrowを使うことの価値についての議論、を提示します。

### 検証 {#validation}

OTLPとOTel Arrowと呼ばれるOTLPのカラム型バージョンとの間で圧縮率を比較するために、一連のテストが実施されました。
主な結果は次のとおりです。

* 単変量時系列については、OTel Arrowは**帯域幅削減の点で2倍から2.5倍優れている一方で、フェーズ1ではエンドツーエンドの速度（OTLPとの相互変換を含む）が1.5倍から2倍遅くなります**。
  **フェーズ2**ではOTLPとArrow間の変換がなくなり、私たちの見積もりではエンドツーエンドの速度が**3.1倍から11.2倍速く**なります。
* 多変量時系列については、OTel Arrowは**帯域幅削減の点で3倍から7倍優れている一方で、エンドツーエンドの速度（OTLPとの相互変換を含む）はフェーズ1の単変量時系列シナリオと同程度です**。
  フェーズ2はまだ見積もられていませんが、同様の結果が期待されます。
* ログについては、OTel Arrowは**帯域幅削減の点で1.6倍から2倍優れている一方で、フェーズ1ではエンドツーエンドの速度（OTLPとの相互変換を含む）が2.0倍から3.5倍遅くなります**。
  **フェーズ2**ではOTLPとArrow間の変換がなくなり、私たちの見積もりではエンドツーエンドの速度が**2.3倍から4.86倍速く**なります。
* トレースについては、OTel Arrowは**帯域幅削減の点で1.7倍から2.8倍優れている一方で、フェーズ1ではエンドツーエンドの速度（OTLPとの相互変換を含む）が1.5倍から2.1倍遅くなります**。
  **フェーズ2**ではOTLPとArrow間の変換がなくなり、私たちの見積もりではエンドツーエンドの速度が**3.37倍から6.16倍速く**なります。

以下の3列のグラフは、単変量のメトリクス、ログ、トレースに対するベンチマークの結果を示しています。
両方のプロトコルについて、ベースラインは非圧縮のOTLPメッセージのサイズです。
削減率は、このベースラインと各プロトコルの圧縮後のメッセージサイズとの比率です。
使用されている圧縮アルゴリズムは、OTLPとOTel Arrowの両方でZSTDです。

![Summary (standard metrics)](img/0156_compression_ratio_summary_std_metrics.png)

以下の3列のグラフでは、前のものとの唯一の違いは、メトリクスが多変量であることです。
ベンチマークは、OTLPよりもOTel Arrowの方が圧縮率がはるかに優れていることを示しています。
これは、OTel Arrowがカラム型表現を活用することで、多変量シナリオにおいてデータをより効率的に圧縮できるためです。

![Summary (multivariate metrics)](img/0156_compression_ratio_summary_multivariate_metrics.png)

以下の積み上げ棒グラフは、各ステップと各バージョンのプロトコルについて、費やされた時間の分布を並べて比較しています。

![Summary of the time spent](img/0156_summary_time_spent.png)
[グラフを拡大表示する](https://raw.githubusercontent.com/lquerel/oteps/main/text/img/0156_summary_time_spent.png)

> 結論として、これらのベンチマークは、バッチ処理という文脈において帯域幅と処理速度を最適化するために、カラム指向のテレメトリーデータプロトコルを統合することの意義を示しています。

### なぜApache Arrowなのか、どう使うのか {#why-apache-arrow-and-how-to-use-it}

[Apache Arrow](https://arrow.apache.org/)は、フラットな（平坦な）データと階層的なデータの両方に対応する、多用途なカラム型フォーマットであり、業界で確立された地位を持っています。
Arrowは次の点に最適化されています。

* 言語にかかわらず実装間で共通のインメモリフォーマットに基づく、カラム指向のデータ交換。
  これにより、シリアライズおよびデシリアライズの機構が不要になり、ゼロコピーが可能になります。
* 最新のハードウェア最適化（例：SIMD）を用いたインメモリの分析処理。
* 大規模なエコシステム（例：データパイプライン、データベース、ストリーム処理など）との統合。
* 言語非依存であること。

これらの特性はすべて、Arrowを汎用的なテレメトリープロトコルとして優れた選択肢にしています。
Apache Arrowの効率的な実装は、ほとんどの言語（Java、Go、C++、Rustなど）に存在します。
Apache Arrowバッファとのコネクタは、よく知られたファイルフォーマット（例：Parquet）やよく知られたバックエンド（例：BigQuery）向けに存在します。
この既存のインフラストラクチャ（[Arrowエコシステム](https://arrow.apache.org/powered_by/)を参照）を再利用することで、OpenTelemetryプロトコルの適用範囲を広げつつ、その開発と統合を加速しています。

OTLPのデータフォーマットをArrowの世界に適応させること（下記参照）は、この提案が説明しようとしている問題の一部にすぎません。
以下のような、他にも多くの設計上の選択とトレードオフが行われています。

- データの構成（すなわちスキーマとソート順）と、圧縮率を最適化するための圧縮アルゴリズムの選定。
- Arrowデータ、メタデータ、辞書のシリアライズ方法、および転送モード（リクエスト・レスポンス方式か双方向ストリームか）の選定。
- システムに導入された多数のパラメーターの最適化。

![In-memory Apache Arrow RecordBatch](img/0156_OTEL%20-%20HowToUseArrow.png)

### 統合戦略とフェーズ分け {#integration-strategy-and-phasing}

このOTEPは、特定のシナリオ（例：大規模バッチ転送のコスト効率、多変量時系列、高度なデータ処理、データの最小化）をより良くサポートするために、カラム型のテレメトリーデータ表現を追加することで、既存のOTelエコシステムを拡張します。
既存のすべてのコンポーネントは、引き続き互換性を保ち、動作し続けます。

段階的な恩恵を得られるように、2段階の統合が提案されています。

#### フェーズ1 {#phase-1}

この提案は、OTLPプロトコルと互換性のある新しいプロトコルとして設計されています。
次の図に示すように、新しいOTel Arrowレシーバーは、この新しいプロトコルをOTLPプロトコルに変換する役割を担います。
同様に、新しいエクスポーターは、OTLPメッセージをこの新しいArrowベースのフォーマットに変換する役割を担います。

![OTel Collector](img/0156_collector_internal_overview.png)

この最初のステップは、**トラフィックの削減**と、**多変量時系列**のネイティブサポートという特定のユースケースに対応することを意図しています。
コミュニティからのフィードバックに基づくと、多くの企業はインターネット経由でテレメトリーデータを転送するコストを削減したいと考えています。
クライアント環境のエッジに統合とトラフィック変換のポイントとして機能するCollectorを追加することで、カラム型フォーマットを活用して冗長なデータを排除し、圧縮率を最適化できます。
これは次の図に示されています。

![Traffic reduction](img/0156_traffic_reduction_use_case.png)

> [!NOTE]
> **1:** 新しいプロトコルが対象でサポートされていない場合を処理するために、フォールバック機構を使用できます。
> この機構については、この[セクション](#protocol-extension-and-fallback-mechanism)で詳しく説明します。

#### フェーズ2 {#phase-2}

フェーズ2は、次のシナリオをより良くサポートするために、Apache Arrowのサポートをエンドツーエンドで、より具体的にはCollectorの内部にまで拡張することを目指しています。
そのシナリオとは、コスト効率、高度なデータ処理、データの最小化です。
Apache Arrowをネイティブにサポートする新しいレシーバー、プロセッサー、エクスポーターが開発されます。
既存のエコシステムのサポートを継続するために、Collector内にOTLP／OTel Arrowの双方向アダプテーション層が開発されます。
次の図は、OTLPとエンドツーエンドのOTel Arrowパイプラインの両方をサポートするCollectorの概要です。

![OTel Arrow Collector](img/0156_collector_phase_2.png)

エンドツーエンドのカラム指向パイプラインを実装することで、次のような多くの恩恵が得られます。

- **ストリーム処理の高速化**
- **CPUとメモリ使用量の削減**
- **エンドツーエンドでの圧縮率の改善**
- **Apache Arrowエコシステム（クエリエンジン、Parquetサポートなど）へのアクセス**

## プロトコルの詳細 {#protocol-details}

プロトコルの仕様は2つの部分から構成されます。
最初のセクションでは、カラム指向のテレメトリーデータをサポートする新しいgRPCサービスについて説明します。
2番目のセクションでは、OTLPエンティティとそれに対応するApache Arrowのエンティティとの間のマッピングを示します。

### ArrowStreamService {#arrowstreamservice}

OTel Arrowは、テレメトリーデータのカラム型エンコーディングと、クライアントとサーバー間でデータを交換するために使用されるgRPCベースのプロトコルを定義します。
OTel Arrowは、テレメトリーデータのエンコーディングにApache Arrowを活用する、双方向のストリーム指向のプロトコルです。

OTLPとOTel Arrowのプロトコルは併用でき、同じTCPポートを使用できます。
そのために、既存の3つのサービス（`MetricsService`、`LogsService`、`TraceService`）に加えて、単一のAPIエンドポイント`ArrowStream`を公開するサービス`ArrowStreamService`を導入します（詳細は[こちらのprotobuf仕様](#appendix-a---protocol-buffer-definitions)を参照してください）。
このエンドポイントは、双方向ストリーミングプロトコルに基づいています。
クライアントメッセージは、Apache Arrowバッファのバッチをエンコードした`BatchArrowRecords`ストリームです（より具体的には[Arrow IPCフォーマット](#arrow-ipc-format)です）。
サーバーメッセージ側は、以前に送信された各`BatchArrowRecords`のステータスを非同期に報告する`BatchStatus`ストリームです。
このエンドポイントに加えて、OTel Arrowプロトコルは、Metrics、Logs、Tracesという各OTLPエンティティの特有の性質に合わせた、複雑なロードバランシングのルーティングルールを容易にするための、3つの追加サービスを提供します。

基盤となるトランスポートを確立した後、クライアントは`ArrowStream`サービスを使用してテレメトリーデータの送信を開始します。
クライアントは、開いたストリーム上でサーバーに対して`BatchArrowRecords`メッセージを継続的に送信し続け、次のシーケンス図に示すように、サーバーから`BatchStatus`メッセージを継続的に受信することを期待します。

![Sequence diagram](img/0156_OTEL%20-%20ProtocolSeqDiagram.png)

> 到達可能な最大スループットを増やすために、クライアントとサーバーの間で複数のストリームを同時に開くことができます。

クライアントがシャットダウンしている場合（例：プロセスが終了しようとしている場合）、クライアントは、保留中のすべての確認応答を受信するか、実装固有のタイムアウトが経過するまで待機します。
これにより、テレメトリーデータの信頼性の高い配信が保証されます。

このサービスのprotobuf定義は次のとおりです。

```protobuf
// Service that can be used to send `BatchArrowRecords` between one Application instrumented with OpenTelemetry and a
// collector, or between collectors.
service ArrowStreamService {
  // The ArrowStream endpoint is a bi-directional stream used to send batch of `BatchArrowRecords` from the exporter
  // to the collector. The collector returns `BatchStatus` messages to acknowledge the `BatchArrowRecords`
  // messages received.
  rpc ArrowStream(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a traces-only Arrow stream.
service ArrowTracesService {
  rpc ArrowTraces(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a logs-only Arrow stream.
service ArrowLogsService {
  rpc ArrowLogs(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a metrics-only Arrow stream.
service ArrowMetricsService {
  rpc ArrowMetrics(stream BatchArrowRecords) returns (stream BatchStatus) {}
}
```

> [!INFORMATION]
> **単項（Unary）RPC対ストリームRPC**: 私たちは、**バッチごとにスキーマと辞書を指定するオーバーヘッドを取り除くために**、ストリーム指向のプロトコルを使用しています。
> レシーバー側では、スキーマと辞書を追跡するための状態が維持されます。
> [Arrow IPCフォーマット](#arrow-ipc-format)は、このパターンに従うように設計されており、辞書を段階的に送信することも可能にします。
> 同様に、小規模なバッチの転送を最適化するために、ZSTD辞書もRPCストリームに転送できます。
> ストリーム指向プロトコルにありがちな落とし穴（例：ロードバランサーを用いたデプロイでの接続の不均衡）を緩和する方法については、実装に関する推奨事項セクションのこの[段落](#traffic-balancing-optimization)を参照してください。

`BatchArrowRecords`メッセージは3つの属性から構成されます。
protobuf定義は次のとおりです。

```protobuf
// Enumeration of all the OTelArrow payload types currently supported by the OTel Arrow protocol.
// A message sent by an exporter to a collector containing a batch of Arrow
// records.
message BatchArrowRecords {
  // [mandatory] Batch ID. Must be unique in the context of the stream.
  int_64 batch_id = 1;

  // [mandatory] A collection of payloads containing the data of the batch.
  repeated ArrowPayload arrow_payloads = 2;

  // [optional] Headers associated with this batch, encoded using hpack.
  bytes headers = 3;
}
```

`batch_id`属性は、現在のストリームのスコープ内でバッチを一意に識別するための識別子です。
これは、サーバーメッセージの`BatchStatus`ストリームでバッチを一意に識別するために使用されます。
この識別子の実装に関する詳細については、[バッチID生成](#batch-id-generation)セクションを参照してください。

`arrow_payloads`属性は、`ArrowPayload`メッセージのリストです。
各`ArrowPayload`メッセージは、カラム型で符号化されたデータのテーブル（例：メトリクス、ログ、トレース、属性、イベント、リンク、エグゼンプラーなど）を表します。
異なる性質を持ち異なるスキーマを持つ複数の相関するIPC Arrowメッセージを、`batch_id`で識別される同一のOTelArrowバッチ内で送信でき、その結果、Collectorや他の処理システムにおいて複雑なロジックなしに1つの単位として処理できます。
`ArrowPayload`のカラムの詳細については、[OTelエンティティとArrowレコードのマッピング](#mapping-otel-entities-to-arrow-records)のセクションを参照してください。

`headers`属性は省略可能で、バッチに関連付けられた追加のHTTPヘッダーを送信するために使用され、hpackでエンコードされます。

より具体的には、`ArrowPayload`のprotobufメッセージは次のように定義されます。

```protobuf
// Enumeration of all the OTel Arrow payload types currently supported by the
// OTel Arrow protocol.
enum ArrowPayloadType {
  UNKNOWN = 0;

  // A payload representing a collection of resource attributes.
  RESOURCE_ATTRS = 1;
  // A payload representing a collection of scope attributes.
  SCOPE_ATTRS = 2;

  // A set of payloads representing a collection of metrics.
  METRICS = 10;                    // Main metric payload
  NUMBER_DATA_POINTS = 11;
  SUMMARY_DATA_POINTS = 12;
  HISTOGRAM_DATA_POINTS = 13;
  EXP_HISTOGRAM_DATA_POINTS = 14;
  NUMBER_DP_ATTRS = 15;
  SUMMARY_DP_ATTRS = 16;
  HISTOGRAM_DP_ATTRS = 17;
  EXP_HISTOGRAM_DP_ATTRS = 18;
  NUMBER_DP_EXEMPLARS = 19;
  HISTOGRAM_DP_EXEMPLARS = 20;
  EXP_HISTOGRAM_DP_EXEMPLARS = 21;
  NUMBER_DP_EXEMPLAR_ATTRS = 22;
  HISTOGRAM_DP_EXEMPLAR_ATTRS = 23;
  EXP_HISTOGRAM_DP_EXEMPLAR_ATTRS = 24;

  // A set of payloads representing a collection of logs.
  LOGS = 30;
  LOG_ATTRS = 31;

  // A set of payloads representing a collection of traces.
  SPANS = 40;
  SPAN_ATTRS = 41;
  SPAN_EVENTS = 42;
  SPAN_LINKS = 43;
  SPAN_EVENT_ATTRS = 44;
  SPAN_LINK_ATTRS = 45;
}

// Represents a batch of OTel Arrow entities.
message ArrowPayload {
  // [mandatory] A canonical ID representing the schema of the Arrow Record.
  // This ID is used on the consumer side to determine the IPC reader to use
  // for interpreting the corresponding record. For any NEW `schema_id`, the
  // consumer must:
  // 1) close the current IPC reader,
  // 2) create a new IPC reader in order to interpret the new schema,
  // dictionaries, and corresponding data.
  string schema_id = 1;

  // [mandatory] Type of the OTel Arrow payload.
  ArrowPayloadType type = 2;

  // [mandatory] Serialized Arrow Record Batch
  // For a description of the Arrow IPC format see:
  // https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc
  bytes record = 3;
}

```

`schema_id`属性は、`ArrowPayload`内に存在するArrowレコードのスキーマを表す一意な識別子です。
このIDは、レシーバー側で特定の種類のArrowレコードのスキーマと辞書を追跡するために使用されます。
この識別子の実装に関する詳細については、[スキーマID生成](#schema-id-generation)セクションを参照してください。

`ArrowPayloadType`列挙型は、ペイロードの`type`を指定します。

`record`属性は、Arrow RecordBatchのバイナリ表現です。

Arrowバッファを'bytes'型のprotobufフィールドに格納することで、一部のprotobuf実装（例：C++、Java、Rust）のゼロコピー機能を活用でき、Arrowのゼロコピーなシリアライズ・デシリアライズフレームワークに頼ることでArrowの恩恵を最大限に引き出せます。

> [!NOTE]
> デフォルトでは、Collectorの設定にかかわらず最良の圧縮率の恩恵を受けられるように、Arrow IPCレベルでZSTD圧縮が有効になっています。
> ただし、特定の設定にとってその方が理にかなう場合には、この圧縮を無効化し、代わりにグローバルなgRPCレベルで有効にすることもできます。

サーバーメッセージストリームでは、`BatchStatus`メッセージは`StatusMessage`のコレクションです。
`StatusMessage`は5つの属性から構成されます。
protobuf定義は次のとおりです。

```protobuf
// A message sent by a Collector to the exporter that opened the data stream.
message BatchStatus {
  repeated StatusMessage statuses = 1;
}

message StatusMessage {
  int64 batch_id = 1;
  StatusCode status_code = 2;
  ErrorCode error_code = 3;
  string error_message = 4;
  RetryInfo retry_info = 5;
}

enum StatusCode {
  OK = 0;
  ERROR = 1;
}

enum ErrorCode {
  UNAVAILABLE = 0;
  INVALID_ARGUMENT = 1;
}

message RetryInfo {
  int64 retry_delay = 1;
}
```

`BatchStatus`メッセージの定義は比較的単純で、基本的に見ての通りです。

サーバーは、成功（'OK'）またはエラー（'ERROR'）のいずれかのステータスで応答することができます。
`OK`を受信することは、Collectorが受信したメッセージがCollectorによって処理されたことを意味します。
サーバーが空の`BatchEvent`を受信した場合、サーバーは成功を返すべきです。

サーバーによってエラーが返される場合、それは大きく2つのカテゴリーに分類されます。
リトライ可能なものと、リトライ不可能なものです。

* リトライ可能なエラーは、テレメトリーデータの処理が失敗したこと、およびクライアントがエラーを記録し、同じデータのエクスポートをリトライしてもよいことを示します。
  これは、サーバーが一時的にデータを処理できない場合に発生することがあります。
* リトライ不可能なエラーは、テレメトリーデータの処理が失敗したこと、およびクライアントは同じテレメトリーデータの送信をリトライしてはならないことを示します。
  そのテレメトリーデータは破棄しなければなりません。
  これは、たとえば、リクエストに不正なデータが含まれていて、サーバーによってデシリアライズやその他の処理ができない場合に発生することがあります。
  クライアントは、このように破棄されたデータのカウンターを保持すべきです。

サーバーは、コードUNAVAILABLEを使用してリトライ可能なエラーを示すべきであり、`error_message`と`retry_info`を通じて追加の詳細を提供してもよいです。

リトライ不可能なエラーを示すために、サーバーはコードINVALID_ARGUMENTを使用することが推奨され、`error_message`を通じて追加の詳細を提供してもよいです。

> [!NOTE]
> [付録A](#appendix-a---protocol-buffer-definitions)には完全なprotobuf定義が含まれています。

### OTelエンティティとArrowレコードのマッピング {#mapping-otel-entities-to-arrow-records}

OTelエンティティは、複数のApache Arrowの`RecordBatch`にバッチ化されます。
Apache Arrowの RecordBatchは、スキーマとArrow配列のコレクションという2つの要素の組み合わせです。
個々のArrow配列、あるいはそのネストされた子要素は辞書エンコードされることがあり、その場合、辞書エンコードされた配列はその辞書への参照を含みます。
一般に、Arrow IPCの実装は、1つの辞書が複数の配列から参照されていることを認識し、それをワイヤ越しに1度だけ送信するため、受信側は辞書を再利用することによるメモリ使用量の恩恵を維持できます。
この提案では、辞書エンコードされた配列は、カーディナリティの低い文字列（またはバイナリ）カラムをエンコードするために使用されます。
ストリーム指向のAPIは、複数のバッチにわたってスキーマと辞書のオーバーヘッドを償却するために活用されます。

Apache Arrowのスキーマは、異なる[型](https://arrow.apache.org/docs/python/api/datatypes.html)を持ち、null許容プロパティの有無を持つカラムを定義できます。
Arrowのメモリレイアウトについての詳細は、この[ドキュメント](https://arrow.apache.org/docs/format/Columnar.html)を参照してください。

各OTelエンティティの種類（メトリクス、ログ、トレース）に対して、特定の明確に定義されたArrowスキーマのセットが使用されます。

現在のOTelメトリクスモデルは、次のUML図にまとめられます。

![OTel Metrics Model](img/0156_OTEL-Metric-Model.png)

葉ノード（この図では緑色で示されています）は、実際にデータが属性とメトリクスのリストとして定義されている場所です。
基本的に、メトリクスノードとリソースノードの関係は多対1の関係です。
同様に、メトリクスノードと計装スコープノードの関係も多対1の関係です。

この提案で選択されたアプローチは、OTelエンティティを複数のArrow RecordBatchに分割することを含みます。
これらの各RecordBatchは、特定のスキーマを持ち、主キーと外部キーの組み合わせを通じて他のRecordBatchと結び付けられます。
この方法論は、圧縮率、クエリのしやすさ、既存のArrowベースのツールとの統合のしやすさとの間で、最適なバランスを提供します。

このカラム型表現の利点を最大化するために、OTel Arrowは、同一データの局所性を高めることで圧縮率を高めるために、カラムの一部をソートします。

最後に、スキーマと辞書を定義するオーバーヘッドを緩和するために、私たちはArrow IPCフォーマットを使用します。
同じスキーマを共有するRecordBatchは、同種のストリームにグループ化されます。
最初に送信されるメッセージには、カラムデータに加えて、スキーマ定義と辞書が含まれます。
それ以降のメッセージには、スキーマを定義する必要がなくなります。
辞書は、その内容が変更されたときにのみ再送信されます。
次の図は、このプロセスを示しています。

> [!NOTE]
> リスト型、構造体型、共用体型のArrowデータ型を使用して、OTelエンティティごとに単一のArrowレコードを使うアプローチは、主にOTel階層の各レベルを独立してソートできないという理由から採用されませんでした。
> このドキュメントで示されているマッピングは、平均してより優れた圧縮率を提供します。

![Arrow IPC](img/0156_OTEL%20-%20Arrow%20IPC.png)

次のセクションでは、各種類の`ArrowPayload`のスキーマについて説明します。
OTLPエンティティから`ArrowPayload`へのマッピングは、OTel Arrow -> OTLPレシーバーを実装できるように、可逆になるよう設計されています。

#### ログのArrowマッピング {#logs-arrow-mapping}

Arrowへのマッピングとしてもっとも単純なので、まずログのペイロードから始めます。
次のER図（実体関連図）は、OTLPログのバッチを表現するために使用される4つのArrowレコードのスキーマを簡潔に説明しています。

`LOGS`エンティティは、`ResourceLogs`と`ScopeLogs`をマージした、`LogRecord`のフラット化された表現を含みます。
`id`カラムは主キーとして機能し、`LogRecord`をそれに対応する属性（`LOG_ATTRS`エンティティに格納されます）と結び付けます。
`resource_id`カラムはキーとして機能し、各`ResourceLogs`インスタンスをそれぞれの属性（`RESOURCE_ATTRS`エンティティに格納されます）と関連付けます。
同様に、`scope_id`カラムはキーとして機能し、各`ScopeLogs`インスタンスをそれに対応する属性（`SCOPE_ATTRS`エンティティに見られます）と結び付けます。

![Logs Arrow Schema](img/0156_logs_schema.png)

これらの各Arrowレコードは、圧縮率を最適化するために特定のカラムでソートされます。
`id`、`resource_id`、`scope_id`はデルタエンコーディングで格納され、圧縮後のサイズを最小化します。
`parent_id`も、デルタエンコーディングの一種である「デルタグループエンコーディング」（詳細は後述します）で格納されます。

属性は、`key`、`type`、そして次のいずれかのカラム、すなわち`str`、`int`、`double`、`bool`、`bytes`、`ser`、の3つ組として表現されます。
`key`カラムは文字列辞書であり、`type`カラムは6つのバリアントを持つ列挙型であり、値のカラムは属性の型に応じて決まります。
`ser`カラムは、属性の型が複雑（例：マップや配列）な場合に、属性の値のCBORエンコーディングを含むバイナリカラムです。
使用されない値のカラムはnull値で埋められます。

`body`は、`body_type`と、次のいずれかのカラム、すなわち`body_str`、`body_int`、`body_double`、`body_bool`、`body_bytes`、`body_ser`、というタプルで表現されます。

この表現にはいくつかの利点があります。

- 各レコードを独立してソートし、圧縮のためによりよくデータを整列できます。
- 主キーと外部キーを使って異なるArrowレコードを結び付けることができ、SQLエンジンとも容易に統合できます。
- 複雑なArrowデータ型（共用体や構造体のリストなど）を避けることで、Arrowエコシステムとの互換性が最適化されます。

> [!NOTE]
> `pdata`ライブラリがサポートを提供するようになれば、複雑な属性値もprotobufでエンコードできる可能性があります。

#### スパンのArrowマッピング {#spans-arrow-mapping}

OTLPトレースに対するアプローチは、ログに対するものと似ています。
主要な`SPANS`エンティティ（すなわちArrowレコード）は、`ResourceSpans`、`ScopeSpans`、`Spans`のフラット化された表現を包含しています。
標準的な属性のセット（すなわちリソース属性、スコープ属性、スパン属性）に加えて、このマッピングでは、スパンイベントとスパンリンクをそれぞれ別個のエンティティ（`SPAN_EVENTS`と`SPAN_LINKS`）として表現します。
これらは`SPANS`エンティティと1対多の関係を持ちます。
これらの各エンティティは、専用の属性エンティティ（すなわち`SPAN_EVENT_ATTRS`と`SPAN_LINK_ATTRS`）とも関連付けられます。

![Traces Arrow Schema](img/0156_traces_schema.png)

同様に、各Arrowレコードは、圧縮率を最適化するために特定のカラムでソートされます。

`end_time_unix_nano`は、タイムスタンプを表すのに必要なビット数を削減するために、デュレーション（`end_time_unix_nano` - `start_time_unix_nano`）として表現されます。

#### メトリクスのArrowマッピング {#metrics-arrow-mapping}

メトリクスに対するマッピングは、もっとも複雑ではありますが、基本的にはログやスパンに適用されたものと同じロジックに従っています。
主要な'METRICS'エンティティは、`ResourceMetrics`、`ScopeMetrics`、`Metrics`のフラット化された表現をカプセル化しています。
異なるメトリクスの種類の間で共通するすべてのカラム（すなわち`metric_type`、`name`、`description`、`unit`、`aggregation_temporality`、`is_monotonic`）は、この主要なエンティティに統合されています。
さらに、メトリクスの各種類のデータポイントを表現するための専用のエンティティが作られており、そのカラムはそれぞれのメトリクスの種類に固有です。
たとえば、`SUMMARY_DATA_POINTS`エンティティは、`id`、`parent_id`、`start_time_unix_nano`、`time_unix_nano`、`count`、`sum`、`flags`のカラムを含みます。
これらの各「データポイント」エンティティは、次のものと結び付けられます。

- データポイント属性のセット（1対多の関係に従います）。
- データポイントエグゼンプラーのセット（同じく1対多の関係に従います）。

エグゼンプラーエンティティは、さらにそれ専用の属性のセットと結び付けられます。

技術的には、`quantile`エンティティは独立したエンティティとしてエンコードされているのではなく、`SUMMARY_DATA_POINTS`エンティティ内の構造体のリストとしてエンコードされています。

![Metrics Arrow Schema](img/0156_metrics_schema.png)

GaugeとSumは、`METRICS`エンティティ内の`metric_type`カラムによって識別され、データポイント用に同じArrowレコード、すなわち`NUMBER_DATA_POINTS`を共有します。

`span_id`と`trace_id`は、デフォルトでは固定長サイズのバイナリ辞書として表現されますが、そのカーディナリティが一定のしきい値（通常は2^16）を超えると、辞書を用いない形式に切り替わることがあります。

いつものように、これらの各Arrowレコードは、圧縮率を最適化するために特定のカラムでソートされます。
このマッピングにより、同じ属性とタイムスタンプを共有する多数のデータポイントを含むメトリクスのバッチは、非常に高い圧縮性を持つことになります（多変量時系列シナリオ）。

> [!NOTE]
> すべてのOTLPタイムスタンプは、ナノ秒精度のエポックタイムスタンプとしてArrowのタイムスタンプで表現されます。
> この表現は、Arrowエコシステムの他の部分との統合を単純化します（たとえばDataFusionでは数多くの時刻・日付関数がサポートされています）。
> 注: aggregation_temporalityは、int8型の辞書インデックスを持つArrow辞書として表現されます。
> このOTLP列挙型は現在3つのバリアントを持ち、将来的にも2^8を超えるバリアントを持つことは想定していません。

## 実装に関する推奨事項 {#implementation-recommendations}

### プロトコル拡張とフォールバック機構 {#protocol-extension-and-fallback-mechanism}

この新しいプロトコルのサポートは段階的にしか進めることができないため、実装者はフェーズ1において次の実装上の推奨事項に従うことをお勧めします。

* `OTelArrow Receiver`: OTLPとOTel Arrowの両プロトコルを、単一のTCPポートでリッスンします。
  目標は、このプロトコル拡張のサポートを透過的かつ自動的にすることです。
  これは、`ArrowStreamService`を同じgRPCリスナーに追加することで実現できます。
  特定の用途をサポートするために、このデフォルトの動作を無効にする設定パラメーターがOTelArrowレシーバーに追加されます。
* `OTelArrow Exporter`: デフォルトでは、OTelArrowエクスポーターは、対象のレシーバーの`ArrowStreamService`エンドポイントへの接続を開始すべきです。
  この接続が、対象で`ArrowStreamService`が実装されていないために失敗した場合、エクスポーターは自動的にOTLPプロトコルの動作にフォールバックしなければなりません。
  このデフォルトの動作を無効にする設定パラメーターを追加してもよいです。

これら2つのルールを実装することで、一般に現在のエコシステムへのOTel Arrowのシームレスで適応的な統合が可能になるはずです。

具体的にはプロトタイプについて、これはOpenTelemetry Collectorのコードベースのフォークですが、私たちはOTelArrowエクスポーターとレシーバーを、`receiver/otelarrowreceiver`と`exporter/otelarrowexporter`の各コンポーネントへの直接の変更のセットとして導出し、両方に新しい`internal/arrow`パッケージを追加しました。
Collectorのリリースのたびに、この互換性の約束を維持するために、私たちはOTel Arrowの変更をメインラインのコンポーネントとマージしています。

OTel Arrowは、専用の`bytes`フィールドを使用して、gRPCのメタデータ（すなわちHTTP/2ヘッダー）を伝達することをサポートしています。
メタデータは、典型的な単項（unary）gRPCリクエストと同様に、[hpack](https://datatracker.ietf.org/doc/rfc7541/)を使用してエンコードされます。

具体的には次のとおりです。

#### OTelArrow/gRPCレシーバー {#otelarrowgrpc-receiver}

Arrowが有効な場合、OTelArrowレシーバーは、標準的な単項gRPCサービスのOTLPと、OTel Arrowのストリームサービスの両方をリッスンします。
各ストリームは、OTel-Arrow-Adapterの[Consumer](https://pkg.go.dev/github.com/f5/otel-arrow-adapter/pkg/otel/arrow_record#Consumer)のインスタンスを使用します。
コンテキスト内に`client.Metadata`を設定します。

#### OTelArrow/gRPCエクスポーター {#otelarrowgrpc-exporter}

Arrowが有効な場合、OTelArrowエクスポーターは固定数のストリームを開始し、ストリームごとのリクエストで`plog.Logs`、`ptrace.Traces`、`pmetric.Metrics`のいずれかのアイテムを繰り返し送信します。
`exporterhelper`のコールバックは、まず利用可能なストリームの取得を試み、利用可能なストリームがない場合（あるいは接続がダウングレードされるまで）はブロックし、その後標準的な単項gRPCのパスにフォールバックします。
ストリーム送信の機構は、呼び出し元のコンテキストの期限を尊重しつつ、`exporterhelper`の機構を通じてこれらのリトライを実行させることで生じる遅延を避けるために、ストリームの再起動によって引き起こされる失敗のリトライを内部で処理します。

各ストリームは、OTel-Arrow-Adapterの[Producer](https://pkg.go.dev/github.com/f5/otel-arrow-adapter/pkg/otel/arrow_record#Producer)のインスタンスを使用します。

サーバーがArrowサービスを認識しないという理由で特定のストリームが失敗した場合、そのストリームは再起動しません。
すべてのストリームがこのように失敗した場合、チャネルを閉じることで接続がダウングレードされ、その時点でエクスポーターはOTLPエクスポーターとまったく同じように振る舞います。

説明したこの機構は、部分的な失敗シナリオに対して脆弱です。
一部のストリームは成功しているが、他のストリームがArrow未サポートによって失敗している場合、呼び出し元が利用可能なストリームを待ってブロックされるため、Collectorのパフォーマンスは低下します。
Arrowとダウングレード機構を通知するために使用される正確なシグナルは、将来の開発のための領域とみなされています。
[ダウングレードすべきかどうかについてのプロトタイプのテストを参照してください。](https://github.com/open-telemetry/otel-arrow-collector/blob/30e0ffb230d3d2f1ad9645ec54a90bbb7b9878c2/exporter/otlpexporter/internal/arrow/stream.go#L152)

### バッチID生成 {#batch-id-generation}

`batch_id`属性は、メッセージ配信機構によって使用されます。
発行される各`BatchArrowRecords`は、一意な`batch_id`と関連付けられなければなりません。
一意性は、`ArrowStreamService`の呼び出しによって開かれたストリームのスコープ内で保証されなければなりません。
この`batch_id`は、対応するバッチの受信と処理を確認応答するために、`BatchStatus`オブジェクト内で使用されます。
このbatch_idを実装するために数値カウンターが使用され、目標はできるだけ簡潔なIDを使用することです。

### スキーマID生成 {#schema-id-generation}

Collector内では、バッチ処理、フィルタリング、エクスポートなどの操作を行うために、互換性のあるスキーマを持つArrowレコードをグルーピングする必要があります。
このグルーピングを行うために、各`ArrowPayload`に対して合成識別子（すなわち`schema_id`）を計算しなければなりません。

私たちは、次のようにスキーマIDを計算することを推奨します。

* 各Arrowスキーマに対して、各カラムの（名前、型、メタデータ）の3つ組のリストを作成します。
* これらの3つ組を辞書式順序に従ってソートします。
* ソートされた3つ組をセパレーターで連結し、これらの識別子を`schema_id`として使用します（あるいは、等価テーブルを介したより短いバージョンを使用します）。

### トラフィックバランシングの最適化 {#traffic-balancing-optimization}

ストリーム指向プロトコルにありがちな落とし穴を緩和するために、プロトコルの実装者には次のことが推奨されます。

* クライアント側: 複数のストリームを並行して作成します（例：10種類のイベントタイプごとに新しいストリームを作成します）。
* サーバー側: 長時間開いているストリームを閉じます（例：1時間ごとにストリームを閉じます）。

これらのパラメーターは設定ファイルで公開され、アプリケーションに応じて調整されなければなりません。

### スロットリング {#throttling}

OTel Arrowはバックプレッシャーのシグナリングを可能にします。
サーバーがクライアントから受信するデータのペースに追いつけない場合、サーバーはその事実をクライアントに知らせるべきです。
クライアントは、サーバーに過負荷をかけないよう、自身をスロットルしなければなりません。

OTel Arrowを使用してバックプレッシャーを通知するために、サーバーはコードUNAVAILABLEでエラーを返すべきであり、`retry_info`属性を通じて追加の詳細を提供してもよいです。

クライアントがこのシグナルを受信した場合、RetryInfoのドキュメントに概説されている推奨事項に従うべきです。

```
// Describes when the clients can retry a failed request. Clients could ignore
// the recommendation here or retry when this information is missing from error
// responses.
//
// It's always recommended that clients should use exponential backoff when
// retrying.
//
// Clients should wait until `retry_delay` amount of time has passed since
// receiving the error response before retrying. If retrying requests also
// fail, clients should use an exponential backoff scheme to gradually increase
// the delay between retries based on `retry_delay`, until either a maximum
// number of retires have been reached or a maximum retry delay cap has been
// reached.
```

retry_delayの値はサーバーによって決定され、実装依存です。
サーバーは、サーバーが回復するのに十分な時間を与えつつ、クライアントがスロットルされている間にデータを破棄してしまうほどには大きくない、retry_delayの値を選択すべきです。

スロットリングは、それぞれ異なる処理能力を持ち、そのため異なるデータ転送レートを必要とする複数の中間ノードを経由して、ソースから宛先まで至る、信頼性の高いマルチホップのテレメトリーデータ配信を実現するうえで重要です。

### 配信保証 {#delivery-guarantee}

OTel Arrowプロトコルは、特に配信保証の観点において、OpenTelemetryプロトコル（OTLP）の仕様に準拠しています。
Collectorは、受信したメッセージが、Collectorのさまざまな段階を通じて適切に処理された場合にのみ、肯定的な確認応答を受け取ることを保証します。

## リスクと緩和策 {#risks-and-mitigations}

悪意のあるトラフィックから保護するために、認証機構が強く推奨されます。
認証がなければ、OTel Arrowレシーバーは、DoSからトラフィック増幅、機密データの送信に至るまで、さまざまな方法で攻撃を受ける可能性があります。
この仕様は、Collectorにすでに存在する認証機構を再利用します。

## トレードオフと緩和策 {#trade-offs-and-mitigations}

### 重複データ {#duplicate-data}

エッジケース（例：再接続時、ネットワークの中断時など）では、確認応答をまだ受信していない場合、クライアントは最近送信したデータが配信されたかどうかを知るすべがありません。
クライアントは通常、配信を保証するためにそのようなデータを再送することを選択し、その結果サーバー側で重複データが生じる可能性があります。
これは意図的な選択であり、テレメトリーデータにとって正しいトレードオフであるとみなされています。
これは、データバックエンド側で冪等な挿入機構を使用することで緩和できます。

### 非互換なバックエンド {#incompatible-backends}

多変量時系列をネイティブにサポートしていないバックエンドでも、これらのイベントを複数の単変量時系列に自動的に変換し、通常どおり動作させることができます。

### 小型デバイス／小規模なテレメトリーデータストリーム {#small-devicessmall-telemetry-data-stream}

カラム指向のプロトコルは、すべてのシナリオにおいて望ましいとは限りません（例：バッチにデータを蓄積するリソースを持たないデバイス）。
このプロトコル拡張は、テレメトリートラフィックの性質に応じて、クライアントがOTLPとOTel Arrowのプロトコルのどちらかを選択できるようにすることで、これらのさまざまなシナリオにより良く対応できるようにします。

## 将来のバージョンと相互運用性 {#future-versions-and-interoperability}

プロトコルの進化と相互運用性の機構に関する限り、この拡張はOTLP仕様に概説されている[推奨事項](0035-opentelemetry-protocol.md#future-versions-and-interoperability)に従います。

## 先行技術と代替技術 {#prior-art-and-alternatives}

私たちは、このプロトコル拡張に、純粋にprotobufベースのカラム型エンコーディングを使用することを検討しました。
プロトタイプの実現とそれと[Apache Arrow](https://arrow.apache.org/)との比較の結果、私たちはこの方向で進めることを思いとどまりました。

私たちはまた、Zedプロジェクトの[VNG](https://zed.brimdata.io/docs/formats/vng)をカラム型のコーディング技術として使用することも検討しました。
このフォーマットには興味深い特性がありますが、このプロジェクトはApache Arrowに匹敵する十分な成熟度にはまだ達していません。

最後に、私たちは（本ドキュメントで説明されているアプローチと同様に）protobufメッセージにカプセル化されたParquetレコードの使用も検討しました。
Parquet表現は圧縮率を改善できる追加のエンコーディングモードをいくつか提供しますが、Parquetはオンラインデータ処理向けに最適化されたインメモリフォーマットとしては設計されていません。
Apache Arrowはこの種のシナリオ向けに最適化されており、圧縮率、処理速度、シリアライズ・デシリアライズ速度の点でもっとも優れたトレードオフを提供します。

## OTel-Arrowのパフォーマンスの監視 {#monitoring-otel-arrow-performance}

[OpenTelemetry Collectorのユーザーは、送受信されるネットワークバイト数を監視する標準的な方法があれば恩恵を受けられます。](https://github.com/open-telemetry/opentelemetry-collector/issues/6638)
[私たちは、Collectorにおいて専用の`obsreport`メトリクスを使用することを提案しています。](https://github.com/open-telemetry/opentelemetry-collector/pull/6712)

これらの提案に関連して、[私たちはまた、Collectorの標準的な回帰テストにOTel-Arrowを含めるために、OpenTelemetry Collector-Contribの`testbed`フレームワークにおける対応する改善も提案しています。](https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/16835)

## 未解決の課題 {#open-questions}

### Arrowエコシステムの他の部分への拡張 {#extending-into-other-parts-of-the-arrow-ecosystem}

テレメトリーデータ処理のためのSQLサポートは、現在のGo版Collectorにおいて未解決の課題として残っています。
OTelArrowの主要なクエリエンジンである[Datafusion](https://github.com/apache/datafusion)はRustで実装されています。
いくつかの解決策が考えられます。
1) Datafusionの上にGoラッパーを作成する、2) OTel Arrowのエンドツーエンドサポートに特化したRust版Collectorを実装する、3) Goで（大規模なプロジェクトとして）SQL/Arrowエンジンを実装する、です。
Datafusionを使用したプルーフオブコンセプトがRustで実装されており、非常に良好な結果を示しています。

Arrow IPCの機構とデータフォーマットはゼロコピーでの使用を意図しているため、たとえばGoベースのOpenTelemetry Collectorの内部であっても、他の言語で書かれたArrowライブラリを使用することが可能であると私たちは考えています。

### より効率的な場合に行指向のトランスポートを選択する {#choosing-row-oriented-transport-when-it-is-more-efficient}

カラム型表現は、大規模で同種のバッチを転送する場合により効率的です。
カラム指向のバッチと行指向のバッチを自動的に組み合わせるハイブリッドなアプローチをサポートすることで、すべてのシナリオをカバーできるようになるでしょう。
最適なデータ表現モードを自動的に選択する戦略の開発は、未解決の課題です。

### 単項gRPCのOTel ArrowとHTTPのOTel Arrow {#unary-grpc-otel-arrow-and-http-otel-arrow}

現在の設計では、OTel Arrowのトランスポートの恩恵を受けるためにgRPCストリームを使用することを求めています。
私たちは、辞書とスキーマ情報の送信を償却するために大規模なリクエストバッチを使う場合には、単項gRPCやHTTPリクエストでもこの恩恵の一部を得られると考えています。
これは今後の研究課題として残っています。

## 将来の可能性 {#future-possibilities}

### さらに統合された圧縮技術 {#further-integrated-compression-techniques}

ZSTDはトレーニングモードを提供しており、選択したデータの種類に合わせてアルゴリズムをチューニングするために使用できます。
このトレーニングの結果は、データの圧縮に使用できる辞書です。
この[辞書](https://facebook.github.io/zstd/#small-data)を使用することで、小規模なバッチの圧縮率を劇的に改善できます。
この将来の開発は、この提案で使用されているgRPCストリームのアプローチと、OTel Arrowのステートフルなプロトコル上でZSTD辞書を送信する機能の両方の上に構築され、最初の数バッチでZSTDアルゴリズムをトレーニングし、その後最適化された辞書でZSTDのエンコーダー・デコーダーの設定を更新できるようになります。

カラムごとのより高度な軽量圧縮アルゴリズム（例：数値カラムに対する[デルタデルタエンコーディング](https://www.vldb.org/pvldb/vol8/p1816-teller.pdf)）をOTel Arrowプロトコルに統合できる可能性もあります。

## 付録A - プロトコルバッファの定義 {#appendix-a---protocol-buffer-definitions}

Arrowベースの OpenTelemetryイベントに対するprotobuf仕様です。

```protobuf
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This protocol specifies the services and messages utilized by the OTel Arrow
// Protocol. OTelArrow represents OTLP entities in a columnar manner using
// Apache Arrow. The primary objective of this new protocol is to optimize
// transport efficiency in terms of compression (phase 1), memory, and CPU usage
// (phase 2).
//
// Note: This protocol is still experimental and subject to change.

syntax = "proto3";

package opentelemetry.proto.experimental.arrow.v1;

option java_multiple_files = true;
option java_package = "io.opentelemetry.proto.experimental.arrow.v1";
option java_outer_classname = "ArrowServiceProto";

// Note the following is temporary
option go_package = "github.com/f5/otel-arrow-adapter/api/experimental/arrow/v1";

// This service can be utilized to transmit `BatchArrowRecords` either from an
// application instrumented with OpenTelemetry to a collector, or between
// multiple collectors.
//
// Note: If your deployment requires to load-balance the telemetry data based on
// the nature of the telemetry data (e.g. traces, metrics, logs), then you should
// use the `ArrowTracesService`, `ArrowMetricsService`, and `ArrowLogsService`.
service ArrowStreamService {
  // The ArrowStream endpoint is a bi-directional stream used to send batch of
  // `BatchArrowRecords` from the exporter to the collector. The collector
  // returns `BatchStatus` messages to acknowledge the `BatchArrowRecords`
  // messages received.
  rpc ArrowStream(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a traces-only Arrow stream.
service ArrowTracesService {
  rpc ArrowTraces(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a logs-only Arrow stream.
service ArrowLogsService {
  rpc ArrowLogs(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// ArrowTracesService is a metrics-only Arrow stream.
service ArrowMetricsService {
  rpc ArrowMetrics(stream BatchArrowRecords) returns (stream BatchStatus) {}
}

// A message sent by an exporter to a collector containing a batch of Arrow
// records.
message BatchArrowRecords {
  // [mandatory] Batch ID. Must be unique in the context of the stream.
  int64 batch_id = 1;

  // [mandatory] A collection of payloads containing the data of the batch.
  repeated ArrowPayload arrow_payloads = 2;

  // [optional] Headers associated with this batch, encoded using hpack.
  bytes headers = 3;
}

// Enumeration of all the OTel Arrow payload types currently supported by the
// OTel Arrow protocol.
enum ArrowPayloadType {
  UNKNOWN = 0;

  // A payload representing a collection of resource attributes.
  RESOURCE_ATTRS = 1;
  // A payload representing a collection of scope attributes.
  SCOPE_ATTRS = 2;

  // A set of payloads representing a collection of metrics.
  METRICS = 10;                    // Main metric payload
  NUMBER_DATA_POINTS = 11;
  SUMMARY_DATA_POINTS = 12;
  HISTOGRAM_DATA_POINTS = 13;
  EXP_HISTOGRAM_DATA_POINTS = 14;
  NUMBER_DP_ATTRS = 15;
  SUMMARY_DP_ATTRS = 16;
  HISTOGRAM_DP_ATTRS = 17;
  EXP_HISTOGRAM_DP_ATTRS = 18;
  NUMBER_DP_EXEMPLARS = 19;
  HISTOGRAM_DP_EXEMPLARS = 20;
  EXP_HISTOGRAM_DP_EXEMPLARS = 21;
  NUMBER_DP_EXEMPLAR_ATTRS = 22;
  HISTOGRAM_DP_EXEMPLAR_ATTRS = 23;
  EXP_HISTOGRAM_DP_EXEMPLAR_ATTRS = 24;

  // A set of payloads representing a collection of logs.
  LOGS = 30;
  LOG_ATTRS = 31;

  // A set of payloads representing a collection of traces.
  SPANS = 40;
  SPAN_ATTRS = 41;
  SPAN_EVENTS = 42;
  SPAN_LINKS = 43;
  SPAN_EVENT_ATTRS = 44;
  SPAN_LINK_ATTRS = 45;
}

// Represents a batch of OTel Arrow entities.
message ArrowPayload {
  // [mandatory] A canonical ID representing the schema of the Arrow Record.
  // This ID is used on the consumer side to determine the IPC reader to use
  // for interpreting the corresponding record. For any NEW `schema_id`, the
  // consumer must:
  // 1) close the current IPC reader,
  // 2) create a new IPC reader in order to interpret the new schema,
  // dictionaries, and corresponding data.
  string schema_id = 1;

  // [mandatory] Type of the OTel Arrow payload.
  ArrowPayloadType type = 2;

  // [mandatory] Serialized Arrow Record Batch
  // For a description of the Arrow IPC format see:
  // https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc
  bytes record = 3;
}

// A message sent by a Collector to the exporter that opened the data stream.
message BatchStatus {
  repeated StatusMessage statuses = 1;
}

message StatusMessage {
  int_64 batch_id = 1;
  StatusCode status_code = 2;
  ErrorCode error_code = 3;
  string error_message = 4;
  RetryInfo retry_info = 5;
}

enum StatusCode {
  OK = 0;
  ERROR = 1;
}

enum ErrorCode {
  UNAVAILABLE = 0;
  INVALID_ARGUMENT = 1;
}

message RetryInfo {
  int64 retry_delay = 1;
}
```

## 用語集 {#glossary}

### Arrow辞書 {#arrow-dictionary}

Apache Arrowでは、テキストやバイナリのカラムを辞書（数値インデックス -> テキスト／バイナリバッファ）としてエンコードできます。
このエンコーディングが使用される場合、カラムにはインデックス値のみが含まれ、辞書は参照のためにスキーマに付加されます。
この種のエンコーディングは、カーディナリティの低い（通常は2^16未満の異なる値を持つ）テキストカラムやバイナリカラムが占める容量を大幅に削減します。
詳細については、Apache Arrowの[ドキュメント](https://arrow.apache.org/docs/python/data.html#dictionary-arrays)を参照してください。

### Arrow IPCフォーマット {#arrow-ipc-format}

[Arrow IPCフォーマット](https://arrow.apache.org/docs/python/ipc.html)は、同種のレコードバッチをストリームモードで効率的に送信するために使用されます。
スキーマはストリームの開始時にのみ送信されます。
辞書は、更新されたときにのみ送信されます。

### 多変量時系列 {#multivariate-time-series}

多変量時系列とは、時間に依存する変数を複数持つ時系列のことです。
それぞれの変数は、自身の過去の値だけでなく、他の変数にもある程度依存しています。
3軸の加速度計が3つのメトリクスを同時に報告する場合、マウスの移動がxとyの値を同時に報告する場合、気象観測所が気温、雲量、露点、湿度、風速を報告する場合、同じ属性を共有する多数の相互に関連するメトリクスによって特徴付けられるHTTPトランザクションの場合など、これらはすべて多変量時系列のよくある例です。

## 謝辞 {#acknowledgements}

リファレンス実装をOTel Collectorに統合する貢献をしてくださった[Joshua MacDonald](https://github.com/jmacd)氏、OTLPプロトコルとの統合戦略の策定を手伝ってくださった[Tigran Najaryan](https://github.com/tigrannajaryan)氏、そしてデータチャートの表現に関する数々の意見交換と助言をくださった[Sébastien Soudan](https://github.com/ssoudan)氏に、特別な感謝を捧げます。

この[PR](https://github.com/open-telemetry/oteps/pull/171)のレビューと検証に参加してくださったすべてのレビュアーの方々に感謝します。

最後に、この取り組みを支援してくださった[F5](https://www.f5.com/)に、深く感謝いたします。

