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


# OTEP-0152: テレメトリースキーマ

* [動機](#motivation)
* [解決策の概要](#solution-summary)
* [スコープ外の事項](#what-is-out-of-scope)
* [ユースケース](#use-cases)
  * [完全なスキーマ対応](#full-schema-aware)
  * [Collectorによるスキーマ変換の支援](#collector-assisted-schema-transformation)
* [スキーマURL](#schema-url)
* [スキーマバージョン番号](#schema-version-number)
* [スキーマファイル](#schema-file)
  * [allセクション](#all-section)
  * [resourcesセクション](#resources-section)
  * [spansセクション](#spans-section)
    * [rename_attributes変換](#rename_attributes-transformation)
  * [span_eventsセクション](#span_events-section)
    * [rename_events変換](#rename_events-transformation)
    * [rename_attributes変換](#rename_attributes-transformation-1)
  * [metricsセクション](#metrics-section)
    * [rename_metrics変換](#rename_metrics-transformation)
    * [rename_attributes変換](#rename_attributes-transformation)
  * [logsセクション](#logs-section)
    * [rename_attributes変換](#rename_attributes-transformation-2)
  * [変換の順序](#order-of-transformations)
  * [スキーマファイルフォーマット番号](#schema-file-format-number)
* [OTLPの変更](#otlp-changes)
* [APIおよびSDKの変更](#api-and-sdk-changes)
* [OpenTelemetryスキーマ](#opentelemetry-schema)
* [パフォーマンスへの影響](#performance-impact)
* [未解決の疑問点](#open-questions)
* [将来的な可能性](#future-possibilities)
  * [親スキーマ](#parent-schema)
  * [スキーマにおける現在の状態](#current-state-in-schema)
  * [その他の変換タイプ](#other-transformation-types)
  * [バージョン間の変換可能性](#version-convertability)
* [検討した代替案](#alternates-considered)
  * [名前のエイリアス](#name-aliases)
  * [スキーマのネゴシエーション](#schema-negotiation)
* [先行技術](#prior-art)
* [付録A. スキーマファイルの例](#appendix-a-example-schema-file)

## 動機 {#motivation}

計装されたアプリケーションのようなテレメトリーソースと、オブザーバビリティバックエンドのようなテレメトリーの消費者は、しばしば発行されるテレメトリーについて暗黙の前提を置きます。
彼らは、テレメトリーが特定の属性を含んでいる、あるいはデータの特定の形状や構成を持っているだろうと想定します（これは本文書を通じて「テレメトリースキーマ」と呼ばれます）。

このため、消費者を壊すことなく発行されるテレメトリーデータの構成を変更することが、困難あるいは不可能になります。
たとえば、計装ライブラリによって作成されたスパンの属性の名前を変更すると、バックエンドがその属性を名前で見つけることを期待している場合、バックエンドが壊れる可能性があります。

セマンティック規約はこの問題の重要な部分です。
これらの規約は、スパン属性、メトリック名、その他のフィールドにどのような名前と値を使うかを定義します。
セマンティック規約が変更されると、既存の実装（テレメトリーソースまたは消費者）も対応して変更する必要があります。
さらに悪いことに、変更されたセマンティック規約に依存し、共に動作するテレメトリーソースの実装とテレメトリー消費者の実装は、同時に変更される必要があります。
そうしなければ、そのような実装は互いに正しく連携して動作しなくなります。

本質的には、1) OpenTelemetryのセマンティック規約、2) テレメトリーソース、3) テレメトリー消費者、という3者の間には結合があります。
この結合が、この3者の独立した進化を複雑にしています。

私たちは以下のニーズを認識しています。

- OpenTelemetryのセマンティック規約は、時間とともに進化する必要があります。
  規約が最初に定義されるとき、間違いが起こり得ますし、私たちはその間違いを時間をかけて修正したいと考えるかもしれません。
  また、属性の分類法についての理解が深まるにつれて、属性を異なる名前空間に再グループ化するために規約を変更したいと考えるかもしれません。

- テレメトリーソースは、時間の経過とともに、発行するテレメトリーのスキーマを変更したいと考えるかもしれません。
  これは、たとえばセマンティック規約が進化し、私たちのテレメトリーを新しく導入された規約に一致させたいためかもしれません。

- オブザーバビリティシステムでは、異なるテレメトリースキーマに準拠したデータを生成するテレメトリーソースが同時に存在する可能性があります。
  なぜなら、異なるソースは異なるペースで進化し、異なる主体によって実装され、制御されるからです。

- テレメトリー消費者は、受信した特定のテレメトリーがどのスキーマに準拠しているかを理解する必要があります。
  消費者はまた、異なるテレメトリースキーマを使用するテレメトリーデータを解釈できる方法を必要とします。

本文書は、これらのニーズに対する解決策を提案します。

## 解決策の概要 {#solution-summary}

私たちは、上記の3者が、正しく連携して動作する能力を継続的に保持しながら、時間とともに独立して進化できるべきだと考えています。

テレメトリースキーマは、これを可能にする方法の中心にあります。
以下が提案の概要です。

- 私たちは、テレメトリースキーマを定義するためのファイルフォーマットを導入します。

- テレメトリースキーマにはバージョンが付けられます。
  時間の経過とともにスキーマは進化する可能性があり、テレメトリーソースはスキーマの新しいバージョンに準拠するデータを発行するかもしれません。

- テレメトリースキーマは、そのような変換が可能である場合に、スキーマの異なるバージョン間でテレメトリーデータを変換するために必要な変換を明示的に定義します。
  変換が不可能な場合、それはバージョン間の破壊的変更を構成します。

- テレメトリースキーマは、スキーマURLによって識別され、各スキーマバージョンごとに一意です。

- テレメトリーソース（計装ライブラリなど）は、発行するテレメトリーにスキーマURLを含めるべきです。

- テレメトリー消費者は、受信したテレメトリーのスキーマに注意を払うべきです。
  必要であれば、テレメトリー消費者は、使用箇所で期待されるターゲットスキーマバージョンに、受信したスキーマバージョンからテレメトリーデータを変換してもよいです（たとえば、ダッシュボードはそれが期待するスキーマバージョンを定義できます）。

- OpenTelemetryは、仕様の一部としてテレメトリースキーマを公開します。
  このスキーマには、セマンティック規約が経る変換のリストが含まれます。
  スキーマは、よく知られたURLで参照およびダウンロードできるようになります。

- OpenTelemetryの計装ライブラリは、発行するすべてのテレメトリーにOpenTelemetryスキーマURLを含めます。

- OTLPは、発行されるテレメトリーにスキーマURLを含めることができるように変更されます。

- サードパーティのライブラリ、計装、またはアプリケーションは、OpenTelemetryスキーマと完全に異なる場合には独自のテレメトリースキーマを定義して公開する（あるいはOpenTelemetryスキーマを使用する）こと、そして発行するテレメトリーにスキーマURLを含めることを推奨されます。

## スコープ外の事項 {#what-is-out-of-scope}

- 本提案で定義されるスキーマの概念は、テレメトリーの形状を完全に記述しようとするものではありません。
  たとえば、このスキーマは属性のすべての有効な値やメトリックの期待されるデータ型などを定義しません。
  それは目標ではありません。
  私たちの目標は、以下の問題のみを解決するために狭く定義されています。
  すなわち、OpenTelemetryセマンティック規約が時間とともに進化できるようにすることです。
  そのため、本文書はスキーマの _完全な状態_ ではなく、スキーマへの _変更_ に関心を持っています。
  とはいえ、これを排除するわけではありません。
  スキーマファイルフォーマットは拡張可能であり、将来的にはスキーマの完全な状態を定義できるようになるかもしれません（将来的な可能性セクションの[スキーマにおける現在の状態](#current-state-in-schema)を参照してください）。

- 私たちは、意図的に、近い将来にOpenTelemetryセマンティック規約が必要とすると考えられるもっとも一般的な変更を処理するために必要な必要最小限に、スキーマの変換タイプを限定します。
  将来的には[より多くの種類の変換が提案される](#other-transformation-types)かもしれません。
  本提案は、私たちが想像できるスキーマへのあらゆる可能な変更を処理できる、包括的な変換タイプの集合をサポートしようとするものではありません。
  それはあまりにも複雑になり、非常に高い確率で不要なものになるでしょう。
  新しい変換タイプは、OpenTelemetryの進化のために必要であるという証拠がある時点で、将来的にスキーマファイルフォーマットに提案・追加されるべきです。

## ユースケース {#use-cases}

このセクションでは、テレメトリースキーマのいくつかの興味深いユースケースを示します（他のユースケースも考えられ、これは網羅的なリストではありません）。

### 完全なスキーマ対応 {#full-schema-aware}

以下は、スキーマに対応したオブザーバビリティシステムの例です。

![Full Schema-Aware](img/0152-otel-schema.png)

テレメトリーデータが発行され、配信され、保存される際に、テレメトリーソースとバックエンドのペアで何が起こるかを詳しく見てみましょう。

![Source and Backend](img/0152-source-and-backend.png)

この例では、テレメトリーソースはOpenTelemetryスキーマのバージョン1.2.0に準拠するスパンを生成しています。
そこでは、スパンが本番環境から来ていることを記録するために「deployment.environment」属性が使用されています。

テレメトリー消費者は、OpenTelemetryスキーマのバージョン1.1.0でテレメトリーを保存したいと考えています。
スキーマトランスレーターは、受信したスパンの schema_url を目的のスキーマと比較し、バージョン変換が必要であることを認識します。
そして、スパンを保存する前に、スキーマファイルに記述されている変更を適用し、属性名を「deployment.environment」から「environment」に変更します。

そして、以下は保存されたデータをクエリするためにスキーマをどのように使用できるかの例です。

![Query Translate](img/0152-query-translate.png)

### Collectorによるスキーマ変換の支援 {#collector-assisted-schema-transformation}

これはやや異なるユースケースであり、バックエンドがスキーマを認識しておらず、バックエンドが受け取ることを期待するスキーマにテレメトリーを変換するために OpenTelemetry Collector に依存しています。
「Schema Translate Processor」が設定され、ターゲットの schema_url が指定され、Collectorを通過するすべてのテレメトリーデータがそのターゲットスキーマに変換されます。

![Collector](img/0152-collector.png)

## スキーマURL {#schema-url}

スキーマURLはスキーマの識別子です。
このURLは、HTTPまたはHTTPSプロトコルを使用して取得できる[スキーマファイル](#schema-file)の場所を指定します（つまり、単なるURIではなくURLです）。

指定されたURLの取得は、HTTPリダイレクトのステータスコードを返す場合があります。
取得側は、HTTP標準に従い、リダイレクトレスポンスを尊重して、リダイレクトされたURLからファイルを取得しなければ（MUST）なりません。

URLパスの最後の部分は、スキーマのバージョン番号です。

```
http[s]://server[:port]/path/<version>
```

`<version>` の前の部分は、スキーマファミリー識別子と呼ばれます。
1つのスキーマファミリー内のすべてのスキーマは、同一のスキーマファミリー識別子を持ちます。

スキーマの新しいバージョンを作成するには、スキーマファミリー内の最後のバージョンのスキーマファイルをコピーし、新しいバージョンの定義を追加します。
新しいバージョンに対応するスキーマファイルは、新しいURLで取得可能でなければなりません。

重要: スキーマファイルは、いったん公開されると不変です。
スキーマファイルを取得したら、永続的にキャッシュすることが推奨されます。
スキーマファイルは、そのファイルを必要とすることが予想されるソフトウェアと一緒にビルド時にパッケージ化されることもあります（たとえば、最新のOpenTelemetryスキーマファイルは、OpenTelemetry Collectorのスキーマ変換プロセッサとともにビルド時にパッケージ化できます）。

## スキーマバージョン番号 {#schema-version-number}

バージョン番号は、SemVer 2.0に似た MAJOR.MINOR.PATCH フォーマットに従います。

バージョン番号は、SemVer 2.0仕様で定義されている[順序付けルール](https://semver.org/#spec-item-11)を使用します。
[変換の順序](#order-of-transformations)で、順序付けがどのように使われるかを参照してください。
順序付けルール以外、スキーマバージョン番号は他のいかなる意味も持ちません。

OpenTelemetryのスキーマバージョン番号は、OpenTelemetry仕様のバージョン番号と一致します。
詳細は[こちら](#opentelemetry-schema)を参照してください。

## スキーマファイル {#schema-file}

スキーマファイルは、特定のバージョンのスキーマを記述するYAMLファイルです。
これは、同じスキーマファミリーの他の古い互換バージョンで表現されたテレメトリーデータを、このスキーマバージョンに変換するために使用できる変換を定義します。

以下は、スキーマファイルの構造です。

```yaml
# Defines the file format. MUST be set to 1.0.0.
file_format: 1.0.0

# The Schema URL that this file is published at. The version number in the URL
# MUST match the highest version number in the "versions" section below.
# Note: the schema version number in the URL is not related in any way to
# the file_format setting above.
schema_url: https://opentelemetry.io/schemas/1.2.0

# Definitions for each schema version in this family.
# Note: the ordering of versions is defined according to semver
# version number ordering rules.
versions:
  <version_number_last>:
    # definitions for this version. See details below.

  <version_number_previous>:
    # definitions for previous version
    ...
  <version_number_first>:
    # Defines the first version.
```

各 `<version_number>` セクションは、以下の構造を持ちます。

```yaml
  <version_number>:
    all:
      changes:
        # sequence of transformations.

    resources:
      changes:
        # sequence of transformations.

    spans:
      changes:
        # sequence of transformations.

    span_events:
      changes:
        # sequence of transformations.

    metrics:
      changes:
        # sequence of transformations.

    logs:
      changes:
        # sequence of transformations.
```

各バージョン定義の下には、「all」、「resources」、「spans」、「span_events」、「metrics」、「logs」の6つのサブセクションがあります。
このリストの最後の5つのサブセクションには、対応するテレメトリーデータタイプにのみ適用される定義が含まれます。
「all」セクションには、すべてのタイプのテレメトリーデータに適用される定義が含まれます。

以下では、各セクションについて詳しく説明します。

### allセクション {#all-section}

スキーマファイルの「all」セクションは変換を定義します。
これには、属性が以前のバージョンからこのバージョンへどのように名前変更されたかを定義する「changes」という名前のサブセクションを含んでいなければ（must）なりません。

「changes」セクションは変換のシーケンスです。
「all」セクションでサポートされる変換は「rename_attributes」変換の1つだけです。

「rename_attributes」変換は、キーと値のペアのマップを必要とします。
ここで、キーは前のバージョンで使用されていた属性の古い名前であり、値はこのバージョンから始まる属性の新しい名前です。
以下がその構造です。

```yaml
    all:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values.
```

「all」セクションの変換は、以下のテレメトリーデータに適用されます。
リソース属性、スパン属性、スパンイベント属性、ログ属性、メトリック属性です。

重要: 前のバージョンから現在のバージョンに変換する場合、「all」セクションの変換シーケンスが最初に実行されます。
その後、変換されているデータタイプに対応する特定のセクション（「resources」、「spans」、「span_events」、「metrics」または「logs」）の変換が適用されます。

「rename_attributes」変換は、ほとんどの場合、可逆であることに注意してください。
逆方向に適用して、テレメトリーデータをこのバージョンから前のバージョンに変換することが可能です。
唯一の例外は、前のバージョンの2つ以上の異なる属性が、新しいバージョンで同じ属性に名前変更される場合です。
その場合、逆変換は曖昧になるため不可能です。
逆変換が不可能な場合、それは互換性のない変更とみなされます。
この場合、新しいバージョンでスキーマのMAJORバージョン番号を上げるべき（SHOULD）です。

### resourcesセクション {#resources-section}

「resources」セクションは、その構造において「all」と非常によく似ています。
「all」セクションと同様に、「resources」セクションの変換には「rename_attributes」変換のみを含めることができます。

「all」セクションとの唯一の違いは、この変換がResourceデータタイプにのみ適用可能であることです。

以下がその構造です。

```yaml
    resources:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # the previous version, the values are the new attribute name
              # starting from this version.
```

### spansセクション {#spans-section}

スキーマファイルの「spans」セクションは、Spanデータタイプにのみ適用可能な変換を定義します。
これには、スパンを前のバージョンからこのバージョンに変換するために適用される一連のアクションを定義する「changes」という名前のサブセクションを含んでいなければ（must）なりません。

「span」セクションでサポートされる変換は「rename_attributes」の1つです。

#### rename_attributes変換 {#rename_attributes-transformation}

これは、「all」セクションと「resource」セクションでサポートされる「rename_attributes」変換に似ています。
加えて、変換を適用すべきスパンを任意で指定することも可能です。
以下がその構造です。

```yaml
    spans:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # in the previous version, the values are the new attribute name
              # starting from this version.
```

### span_eventsセクション {#span_events-section}

スキーマファイルの「spans_events」セクションは、SpanのEventデータタイプにのみ適用可能な変換を定義します。
これには、イベントを前のバージョンからこのバージョンに変換するために適用される一連のアクションを定義する「changes」という名前のサブセクションを含んでいなければ（must）なりません。

「spans_events」セクションでサポートされる変換は、「rename_events」と「rename_attributes」の2つです。

#### rename_events変換 {#rename_events-transformation}

この変換により、イベント名を変更できます。
これはすべてのイベントに適用されるか、指定された名前を持つスパンのイベントにのみ適用されます。
以下がその構造です。

```yaml
    span_events:
      changes:
        - rename_events:
            name_map:
              # The keys are old event name used in the previous version, the
              # values are the new event name starting from this version.
```

#### rename_attributes変換 {#rename_attributes-transformation-1}

これは、「all」セクションと「resource」セクションでサポートされる「rename_attributes」変換に似ています。
加えて、変換を適用すべきスパンとイベントを任意で指定することも可能です（両方の任意条件が指定されている場合、変換が適用可能であるためには両方が一致しなければなりません）。
以下がその構造です。

```yaml
    span_events:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # in the previous version, the values are the new attribute name
              # starting from this version.

            apply_to_spans:
              # Optional span names to apply to. If empty applies to all spans.

            apply_to_events:
              # Optional event names to apply to. If empty applies to all events.
```

### metricsセクション {#metrics-section}

スキーマファイルの「metrics」セクションは、Metricデータタイプにのみ適用可能な変換を定義します。
これには、メトリックを前のバージョンからこのバージョンに変換するために適用される一連のアクションを定義する「changes」という名前のサブセクションを含んでいなければ（must）なりません。

「metrics」セクションでサポートされる変換は、「rename_metrics」と「rename_attributes」の2つです。

#### rename_metrics変換 {#rename_metrics-transformation}

この変換により、メトリック名を変更できます。
これはすべてのメトリックに適用されます。
以下がその構造です。

```yaml
    metrics:
      changes:
        - rename_metrics:
            # map of key/values. The keys are the old metric name used
            # in the previous version, the values are the new metric name
            # starting from this version.
```

#### rename_attributes変換 {#rename_attributes-transformation}

これは、「span」セクションでサポートされる「rename_attributes」変換に似ています。
以下がその構造です。

```yaml
    metrics:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # in the previous version, the values are the new attribute name
              # starting from this version.

            apply_to_metrics:
              # Optional. If it is missing the transformation is applied
              # to all metrics. If it is present the transformation is applied
              # only to the metrics with the name that is found in the sequence
              # specified below.
```

### logsセクション {#logs-section}

スキーマファイルの「logs」セクションは、Log Recordデータタイプにのみ適用可能な変換を定義します。
これには、ログを前のバージョンからこのバージョンに変換するために適用される一連のアクションを定義する「changes」という名前のサブセクションを含んでいなければ（must）なりません。

「logs」セクションでサポートされる変換は「rename_attributes」の1つです。

#### rename_attributes変換 {#rename_attributes-transformation-2}

これは、「spans」セクションでサポートされる「rename_attributes」変換に似ています。
以下がその構造です。

```yaml
    logs:
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # the previous version, the values are the new attribute name
              # starting from this version.
```

### 変換の順序 {#order-of-transformations}

古いバージョンXから、同じスキーマファミリーに属する新しいバージョンYへ変換する場合、範囲[X..Y]内の各バージョンで指定された変換が、1つずつ適用されます。
すなわち、まずXからX+1に変換し、次にX+1からX+2に、……、Y-2からY-1に、Y-1からYに変換します。
（注: バージョン番号は整数の連続体ではありません。
バージョン番号に自然数1を加えるという考え方は、「このスキーマファミリーに対して定義されている次に新しいバージョン番号」というフレーズのプレースホルダーです。）

特定のバージョンXについて列挙されている変換は、バージョンXの直前にある、同じスキーマファミリーに属するスキーマバージョン以降に起きた変更を記述しています。
これらの変換は、「all」、「resources」、「spans」、「span_events」、「metrics」、「logs」の6つのセクションに列挙されています。
以下は、変換が適用される順序です。

- 「all」セクションの変換は、他の5つのセクションのいずれの変換よりも常に先に適用されます。

- 「spans」セクションの変換は、「span_events」セクションの変換より先に適用されます。

- 残りのセクション（「resources」、「metrics」、「logs」）の変換が互いに、あるいは「spans」セクションに対して、どの順序で適用されるかは未定義です（相互依存性がないため、順序は問題になりません）。

これら6つのセクションのそれぞれの「changes」サブセクションでは、変換のシーケンスはスキーマファイルに列挙されている順に、上から下へ適用されます。

新しいバージョンYから古いバージョンXへ、逆方向に変換する場合、上記に列挙された変換の順序は正確に逆になり、それぞれの個々の変換も逆変換を実行します。

### スキーマファイルフォーマット番号 {#schema-file-format-number}

スキーマファイル内の「file_format」設定は、ファイルのフォーマットバージョンを指定します。
フォーマットバージョンは、SemVer 2.0に似た MAJOR.MINOR.PATCH フォーマットに従います。

「file_format」設定は、ファイルの消費者が、そのファイルの内容を解釈できるかどうかを知るために使用されます。

この設定の現在の値は「1.0.0」であり、本OTEPが受理された時点でOpenTelemetry仕様に公開されます。
この番号への変更は、OTEPプロセスに従い、仕様に公開されなければ（MUST）なりません。

現在のスキーマファイルフォーマットでは、限定された種類のテレメトリーデータの変換のみを表現できます。
私たちは、将来的により多くの種類の変換をサポートすることが望ましくなる、あるいはスキーマファイルに記録することが望ましいその他の追加情報が出てくることを予想しています（[将来的な可能性](#future-possibilities)を参照してください）。

スキーマファイルフォーマットが時間とともに進化するにつれて、フォーマットバージョン番号は以下のルールに従って変更されるべき（SHOULD）です。

- PATCH番号は、ファイルの既存の消費者に影響を与えない方法でファイルフォーマットが変更される場合に、増加させるべき（SHOULD）です。
  たとえば、既存のセクションに影響を与えず、既存のスキーマ機能に何の影響も与えない、スキーマファイル内の完全に新しいセクションの追加は、PATCH番号を増加させるだけで行うことができます。
  このアプローチは、ファイル内の新しい設定が、既存のすべての処理ロジックによって完全かつ安全に無視できる場合にのみ有効です。

  たとえば、「changes」セクションのみを気にする既存の消費者には、スキーマの完全な状態を記述する完全に新しいセクションを追加しても影響はありません（スキーマの変更を処理する際に考慮する _必要がある_ ように新しいセクションのセマンティクスを明示的に定義しない限り）。
  したがって、そのような新しいセクションの追加は、PATCH番号の増加を使って行うことができます。

- MINOR番号は、ファイルフォーマットに後方互換な方法で新しい設定が追加された場合に、増加させるべき（SHOULD）です。
  このコンテキストにおける「後方互換」とは、MAJORバージョン番号が同じであれば、新しいMINOR番号を認識している消費者が、その特定のMINORバージョン番号、あるいはそれより低い任意のMINORバージョン番号のファイルを消費できることを意味します。
  通常、これは、ファイルフォーマットに追加された設定が任意であり、その設定のデフォルト値が以前のファイルフォーマットバージョンの動作に一致することを意味します。

  注: MINORバージョン番号に基づく「前方互換性」はありません。
  特定のMINORバージョン番号まで読み込みをサポートする消費者は、それより高いMINORバージョン番号のファイルを消費しようとするべきではありません（SHOULD NOT）。

- MAJOR番号は、ファイルフォーマットが互換性のない方法で変更された場合に、増加させるべき（SHOULD）です。
  たとえば、「changes」セクションに新しい変換タイプを追加することは、既存のスキーマ変換ロジックによって無視できないため互換性のない変更となり、そのような変更には新しいMAJOR番号が必要になります。

対応して、以下のようになります。

- スキーマファイルの消費者は、MAJORバージョン番号が消費者がサポートするものと（高くても低くても）異なる場合、スキーマファイルを解釈しようとするべきではありません（SHOULD NOT）。

- スキーマファイルの消費者は、MINORバージョン番号が消費者がサポートするものより高い場合、スキーマファイルを解釈しようとするべきではありません（SHOULD NOT）。

- 消費者は、PATCH番号を無視してもよい（MAY）です。

いくつかの例で示すと、以下のようになります。

<table>
  <tr>
   <td><strong>ファイルフォーマットバージョン</strong>
   </td>
   <td><strong>消費者が期待するバージョン</strong>
   </td>
   <td><strong>消費者は読み込めるか</strong>
   </td>
  </tr>
  <tr>
   <td>1.0.0
   </td>
   <td>1.0.0
   </td>
   <td>はい
   </td>
  </tr>
  <tr>
   <td>1.0.x
   </td>
   <td>1.0.y
   </td>
   <td>任意のxとyについて、はい。
   </td>
  </tr>
  <tr>
   <td>1.a.x
   </td>
   <td>1.b.x
   </td>
   <td>a&lt;bであればはい、そうでなければいいえ。
   </td>
  </tr>
  <tr>
   <td>2.0.0
   </td>
   <td>1.x.y
   </td>
   <td>いいえ
   </td>
  </tr>
</table>

## OTLPの変更 {#otlp-changes}

発行されるテレメトリーでスキーマURLを運べるようにするには、OTLPメッセージに schema_url フィールドを追加する必要があります。

以下のメッセージに schema_url フィールドを追加します。

```protobuf
message ResourceSpans {
  ...
  // This schema_url applies to the "resource" field and to all spans and span events
  // in the "instrumentation_library_spans" except the spans and span events which
  // have a schema_url specified in the nested InstrumentationLibrarySpans message.
  string schema_url = 3;
}
message InstrumentationLibrarySpans {
  ...
  // This schema_url applies to all spans in the "spans" field regardless of the
  // value of the schema_url field in the outer ResourceSpans message.
  string schema_url = 3;
}

message ResourceMetrics {
  ...
  // This schema_url applies to the "resource" field and to all metrics in the
  // "instrumentation_library_metrics" except the metrics which have a schema_url
  // specified in the nested InstrumentationLibraryMetrics message.
  string schema_url = 3;
}
message InstrumentationLibraryMetrics {
  ...
  // This schema_url applies to all metrics in the "metrics" field regardless of the
  // value of the schema_url field in the outer ResourceMetrics message.
  string schema_url = 3;
}

message ResourceLogs {
  ...
  // This schema_url applies to the "resource" field and to all logs in the
  // "instrumentation_library_logs" except the logs which have a schema_url
  // specified in the nested InstrumentationLibraryLogs message.
  string schema_url = 3;
}
message InstrumentationLibraryLogs {
  ...
  // This schema_url applies to all logs in the "logs" field regardless of the
  // value of the schema_url field in the outer ResourceLogs message.
  string schema_url = 3;
}
```

ResourceSpans、ResourceMetrics、ResourceLogsメッセージ内の schema_url フィールドは、含まれるResource、Span、SpanEvent、Metric、LogRecordメッセージに適用されます。

InstrumentationLibrarySpansメッセージ内の schema_url フィールドは、含まれるSpanおよびSpanEventメッセージに適用されます。

InstrumentationLibraryMetricsメッセージ内の schema_url フィールドは、含まれるMetricメッセージに適用されます。

InstrumentationLibraryLogsメッセージ内の schema_url フィールドは、含まれるLogRecordメッセージに適用されます。

Resourceメッセージと、それに含まれるInstrumentationLibraryメッセージの両方で schema_url フィールドが空でない場合、InstrumentationLibraryメッセージの値が優先されます。

## APIおよびSDKの変更 {#api-and-sdk-changes}

### 計装ライブラリのスキーマURL {#instrumentation-library-schema-url}

OpenTelemetry APIは、（すでにサポートされている計装ライブラリ名とバージョンとの関連付けに加えて）スキーマURLに関連付けられたTracer/Meter/LogEmitterを取得できるように変更される必要があります。

この変更は、すでに安定版として宣言されているAPI（特にGet Tracer API）を壊さないように行う必要があります。

言語によって、以下のようなアプローチが可能です。

- 対応するプロバイダーのGet Tracer/Get Meter/Get LogEmitterメソッドに、3番目の任意パラメータ `schema_url` を追加します。
  これは、ABIの安定性が私たちの保証の一部である言語には、正しいアプローチではないかもしれません。
  なぜなら、それはABIを壊す可能性が高いからです。

- Tracer/Meter/LogEmitterを取得するために、3つのパラメータ（計装ライブラリ名、バージョン、スキーマURL）を渡すことができるメソッドオーバーロードを追加します。
  これは、メソッドオーバーロードが可能な言語で好ましいアプローチである可能性が高いです。

- 上記2つのアプローチのどちらも非破壊的な方法で実行できない場合、APIはTracer/Meter/LogEmitterインスタンスに `SetSchema(schema_url)` メソッドを導入してもよいです。
  このメソッドは1回だけ呼び出されなければ（MUST）ならず、インスタンスを使用してテレメトリーが発行される前に呼び出されなければ（MUST）なりません。

スキーマURLとの関連付けを可能にするために、APIを変更する他の方法もあるかもしれません。
言語のメンテナーは、それぞれの言語に慣用的な方法を選ぶべき（SHOULD）です。

Tracer/Meter/LogEmitterにスキーマURLを関連付ける効果は、関連付けられたTracer/Meter/LogEmitterで発行されたすべてのテレメトリーについて、InstrumentationLibrarySpans、InstrumentationLibraryMetrics、InstrumentationLibraryLogsメッセージ内の schema_url が、提供されたスキーマURLの値で埋められることであるべき（SHOULD）です。

Tracer/Meter/LogEmitterがスキーマURLに関連付けられていない場合、エクスポーターはOTLPメッセージ内の schema_url フィールドを未設定のままにしなければ（MUST）なりません。
その場合、アプリケーション全体のスキーマURLが[適用されます](#application-wide-schema-url)。

未解決の疑問点: 計装ライブラリが特定のOpenTelemetryスキーマバージョンを容易に参照できるようにし、かつライブラリが使用するセマンティック規約のヘルパー（セマンティック規約を定義する定数など）が、まさにその同じスキーマバージョンと一致することをどのように保証すればよいでしょうか。
1つの可能な解決策は、ライブラリが使用できるスキーマバージョンごとのヘルパーパッケージ、たとえばセマンティック規約を定義する定数と対応するスキーマバージョンURLを導入することです。
これはおそらく後続のOTEPのトピックとなるべきでしょう。

### アプリケーション全体のスキーマURL {#application-wide-schema-url}

SDKインターフェースは、ユーザーがアプリケーション全体のスキーマURLを任意で設定できる方法を提供しなければ（MUST）なりません。
このスキーマURLは、OTLPエクスポーターによって発行されるすべてのResourceSpans、ResourceMetrics、ResourceLogsメッセージに埋め込まれます。

ユーザーがアプリケーション全体のスキーマURLを設定しない場合、現在のスキーマURLがOTLPエクスポーターによってメッセージに埋め込まれなければ（MUST）なりません。
ここで「現在の」とは、SDKがコーディングされているOpenTelemetryスキーマのバージョンを意味します。

計装ライブラリに関連付けられたスキーマURLが存在する場合、それは[こちら](#otlp-changes)で説明されているように、アプリケーション全体のスキーマURLよりも優先されることに注意してください。

## OpenTelemetryスキーマ {#opentelemetry-schema}

OpenTelemetryは、`https://opentelemetry.io/schemas/<version>` で独自のスキーマを公開します。
このスキーマのバージョン番号は、そのスキーマを公開する仕様のバージョン番号と同じです。
新しい仕様バージョンがリリースされるたびに、対応する新しいスキーマが同時にリリースされなければ（MUST）なりません。
仕様のリリースが何も変更を導入しなかった場合、スキーマファイル内の対応するバージョンの「changes」セクションは空になります。

本提案の時点で、仕様はバージョン1.2.0にあり、仕様とともに公開された場合、対応するスキーマファイルは以下のようになります。

```yaml
file_format: 1.0.0
schema_url: https://opentelemetry.io/schemas/1.2.0
versions:
  1.2.0:
```

1.2.0はOpenTelemetryスキーマの最初に公開されたバージョンであるため、「changes」セクションはなく、それ以前のバージョンについて記録することが何もないため、ファイルから以前のすべてのバージョンを省略しています。

すべてのOpenTelemetryの計装ソリューションは、このスキーマに従います。

## パフォーマンスへの影響 {#performance-impact}

スキーマ変換が不要である限り、OTLPプロトコルへの変更によるパフォーマンスへの影響はごくわずかです。
テレメトリーソースによる schema_url の記録のコストと、テレメトリー消費者による schema_url フィールドのチェックのコストは、他のコストと比較して無視できるほど小さいものです。
テレメトリースキーマが期待されるスキーマと一致する場合、追加の作業はまったく発生しません。

スキーマバージョンが一致せず変換を実行する必要がある場合、スキーマ変換のパフォーマンスへの影響は、変換の量と種類によって大きくなる可能性があります。
私たちは、Go実装を使用してスパンとメトリックのスキーマ変換をあるバージョンから別のバージョンに実行する1つのユースケースを[ベンチマーク](https://github.com/tigrannajaryan/telemetry-schema/blob/main/schema/perf_test.go)しました。

このベンチマークは以下を行います。

- スキーマ変換に必要なCPU時間と、protobufデコードに必要な時間を比較します。
  これは有用な比較です。
  なぜなら、テレメトリー消費者はprotobufデコードを実行する必要があるため、それが追加の変換作業の影響を測定するための最小限のベースラインとなるからです。

- セマンティック規約の20個の属性が名前変更されるという仮想的なスキーマバージョンの変更を使用します。

- 各バッチが10個の属性を持つ100個のスパン、または1データポイントあたり2個の属性を持つ100個のメトリックデータポイント（Int64 Gauge型）で構成されるデータを使用します。
  各バッチは、20個の属性を持つ1つのリソースに関連付けられています。

以下がベンチマークの結果です。

```
BenchmarkDecode/Trace/Attribs-8             6121            919271 ns/op
BenchmarkDecode/Metric/Int64-8              9516            635418 ns/op
BenchmarkDecodeAndConvertSchema/Trace/Attribs-8  5988       943158 ns/op
BenchmarkDecodeAndConvertSchema/Metric/Int64-8   8588       653266 ns/op
```

BenchmarkDecodeはデコード時間のみです。
BenchmarkDecodeAndConvertSchemaは、デコードとスキーマ変換の時間の合計です。
変換を行うための処理時間は、デコード時間の約3%であることがわかります。

行われたベンチマークは、あくまで例示を目的としたものです。
実際の結果は、データの構成、変換中に行われる変換の量、使用するプログラミング言語などに依存します。
しかし、私たちはこの1つのデータポイントが、潜在的な影響が許容可能であることを理解する上でなお有用であると感じています（そして、それはほとんどのテレメトリー消費者が行う全体の処理のごく一部にとどまる可能性が高いです）。

## 未解決の疑問点 {#open-questions}

- 自由に破壊してよく、厳密なスキーマチェックの対象外である「プレリリース」のセマンティック規約という概念をサポートする必要があるでしょうか。
  おそらく、そのようなセマンティック規約が変更されたときに新しいスキーマバージョンの導入を単に避けることで、これはすでに可能です。

- 計装ライブラリが特定のOpenTelemetryスキーマバージョンを容易に参照できるようにし、かつライブラリが使用するセマンティック規約のヘルパー（セマンティック規約を定義する定数など）が、まさにその同じスキーマバージョンと一致することをどのように保証すればよいでしょうか。
  1つの可能な解決策は、ライブラリが使用できるスキーマバージョンごとのヘルパーパッケージ、たとえばセマンティック規約を定義する定数と対応するスキーマバージョンURLを導入することです。
  これはおそらく後続のOTEPのトピックとなるべきでしょう。

- OTLPリクエストの受信者がスキーマファイルを取得する必要がないように（ネットワークの問題がある場合、それが不可能な可能性があるため）、スキーマURLに加えてスキーマファイル全体をOTLPリクエストに含めることを可能にするべきでしょうか。

## 将来的な可能性 {#future-possibilities}

### 親スキーマ {#parent-schema}

スキーマは、任意で _親_ スキーマを持つことができます（たとえば、カスタムスキーマはOpenTelemetryスキーマをベースにすることができ、その場合、OpenTelemetryスキーマが親になります）。
親を持たないスキーマは _ルート_ スキーマと呼ばれます（したがって、OpenTelemetryスキーマはルートスキーマになります）。
複数のルートスキーマを持つこともあります（これは興味深い可能性を開きますが、この議論は本提案のスコープ外です）。

したがって、すべてのスキーマは、ノードがスキーマバージョンを表し、ノードのペアが2種類のエッジ、すなわち1) 親子関係、2) 同じスキーマファミリー内の連続するバージョン、によって接続される、ルート付き木の集合を形成します。
木の各エッジは、スキーマ変換を表します。

スキーマの木ができれば、あるノードから、エッジを通るパスで接続されている別のノードへ変換することが可能になります。

親スキーマのこの概念が必要であるとわかった場合、この分野についてさらなる研究が必要です。

### スキーマにおける現在の状態 {#current-state-in-schema}

本OTEPのスキーマファイルは、バージョンごとの変更を記述します。
これは、バージョン間の変換を行う上では非常に有用ですが、特定のバージョンの現在の状態を捉えるものではありません。

私たちは、（「changes」セクションに加えて）各特定バージョンの完全な現在のスキーマをスキーマファイルで指定できる機能を追加できます。
これにより、いくつかの興味深い可能性が開けます。

- 発行されたテレメトリーが宣言されたスキーマに準拠していることの自動検証。

- OpenTelemetryセマンティック規約のドキュメントを、この正式なスキーマファイルから自動的に生成できます。
  これにより、仕様内のYAMLファイルからセマンティック規約を成文化・生成する必要がなくなります。

- テレメトリーの消費者は、この情報を使って受信したテレメトリーを解釈できます。

- スキーマファイル内の「changes」セクションを、バージョンの完全な状態間の差分として自動生成できる可能性があります。

「現在の状態」として記録できるものの例としては、Log RecordのBodyのエンコーディングがあります。
これは[以前の提案](https://docs.google.com/document/d/1ZExye1lW43owwaxcbjOvl0P2qER-UaYd_MyxItc2h0k/edit#)の中で概念として説明されています。
その提案は、Log RecordのBodyフィールドのエンコーディングを記録することを提案していました。
これは、スキーマファイルの「logs」セクションの「現在の状態」における新しい設定として実装することもできます。

### その他の変換タイプ {#other-transformation-types}

本OTEPは限定された変換の集合を導入していますが、意図的に変換の種類を最小限に抑えています。
テレメトリーが時間とともに他の、より洗練された方法で進化できることは容易にわかります（たとえば[このリスト](https://github.com/open-telemetry/opentelemetry-specification/issues/1324)を参照してください）。

現在の変換の集合では表現できない、テレメトリースキーマの特定の変更を表現する必要がある場合、将来的により多くの変換タイプを追加できます。
ただし、特にテレメトリーデータの局所的な部分に適用できず、完全な状態を必要とする（たとえば集約などの）変換のような、不必要な、あるいは過度に複雑な変換タイプの導入は、テレメトリースキーマの概念をサポートしたいと考えるテレメトリーソースに大きな実装負担をかける可能性があるため、注意が必要です。

### バージョン間の変換可能性 {#version-convertability}

起きた変更によっては、あるバージョンから別のバージョンへ、損失なく曖昧さなくテレメトリーを変換することが可能な場合と不可能な場合があります。
そのような変換が可能な場合、私たちはそのスキーマがある特定のバージョンから別のバージョンへ「変換可能」であると言います。

一般的に、可能な変換の集合とバージョンXとYのペアが与えられた場合、XからYへテレメトリーを変換することは可能であっても、逆方向、すなわちYからXへの変換は不可能かもしれません。

本提案で定義される変換は、古いスキーマバージョンから新しいバージョンへのすべての変換を可能にします。
逆方向は、場合によっては不可能かもしれません（たとえば、[allセクション](#all-section)における可逆な変換についての説明を参照してください）。

将来的には、スキーマバージョンを明示的に変換不可能と宣言する機能を追加したいと考えるかもしれません。
これは、スキーマが互換性のない方法で変更されたが、スキーマファイルの変換だけではその事実を表現するのに十分な表現力がない、という事実を表現するために必要になるかもしれません。

## 検討した代替案 {#alternates-considered}

### スキーマの凍結 {#freeze-schema}

正式なスキーマ、スキーマファイル、バージョンを導入する代わりに、計装が一度作成されたら決して変更されないことを要求することもできます。
属性は決して名前変更されず、発行されるテレメトリーは常に同じままです。
ただし、既存のテレメトリー消費者に影響を与えない変更は例外です。

これは検討されましたが、受け入れ可能な提案ではないようです。
間違いは起こりますし、セマンティック規約が誤って定義されていたために変更する必要があるかもしれませんし、修正する必要がある計装のバグがあるかもしれません。
私たちは、スキーマを凍結し、発行されるテレメトリーにおいて完全に後方互換な変更のみを許可しようとすることは、あまりにも制限的であり、長期的に従うことが非常に困難であると考えています。

### 名前のエイリアス {#name-aliases}

このアプローチは、スキーマの進化の問題のより小さなサブセット、すなわち属性、メトリックなどの名前の変更を解決します。

このような変更が発生した場合、テレメトリーソースは古い属性名を使用してテレメトリーを生成し続けることができ、さらに新しい名前を持ち、古い属性とまったく同じ値を持つ属性を追加することもできます。
つまり、テレメトリーデータには同時に属性のエイリアスが記録されているということです。

新旧両方の名前が発行されるあらゆるテレメトリーに存在するため、テレメトリーの消費者は、関心のある属性が存在すると単に想定し続けることができます。
このアプローチには正式なスキーマ管理はありません。
私たちは単に、新旧両方の消費者が探しているビットを見つけられるように、同じテレメトリーデータを発行します。

このアプローチの利点は、本文書で提案されているものよりもはるかに単純であることです。

このアプローチの欠点は以下の通りです。

- 限定された種類のスキーマ変更しか処理できません。
  より正式なスキーマの概念によって容易に処理できる一部の変更が、エイリアスのアプローチを使用すると失敗することを容易に示すことができます（たとえば、属性名XとYの入れ替えは、エイリアスでは処理できません）。

- 時間の経過とともに、より多くのデータが公開されるべきであり、公開されるデータの量が増加します。
  エイリアスがどのように正確に処理されるかによって、多くを複製する必要があるかもしれません（たとえば、メトリック名が変更された場合、まったく同じメトリックを2回生成しなければならず、実質的にトラフィックが重複します）。
  あるいは、エイリアスをネイティブに許可するために、OpenTelemetry APIまたはプロトコルにおける破壊的な変更が必要になるかもしれません。

### スキーマのネゴシエーション {#schema-negotiation}

スキーマの変換を実行する代わりに、テレメトリー消費者（バックエンド）に、テレメトリーソースから受信したいバージョンをネゴシエーションさせます。

このアプローチの利点は、バックエンドが1つのスキーマのみをサポートすればよく、変換ルールを定義したり変換を行ったりする必要がまったくないことです。

欠点は以下の通りです。

- テレメトリーソースが複数の異なるスキーマバージョンでテレメトリーを発行できる必要があり、これはテレメトリーソースにとって大きな負担となります。
  これはおそらく致命的な問題です。

- テレメトリー消費者からテレメトリーソースへの通信チャネルが必要ですが、現在それは存在しません（現在、すべての通信はソースから消費者への一方向です）。

## 先行技術 {#prior-art}

- Christian Beedgen氏による[OpenTelemetry Log Data Model: Body Metadata](https://docs.google.com/document/d/1ZExye1lW43owwaxcbjOvl0P2qER-UaYd_MyxItc2h0k/edit#)。

- Josh MacDonald氏による[Generic event encoding schemas](https://docs.google.com/document/d/11ccT_zBbiCfwKyi6TMuy2sA3nUdElNQsDJOQ79icVKs/edit#)。

- David Poncelow氏による[Structured Logging Payloads](https://docs.google.com/document/d/1Xu2tCU5vjw8RNqzqFD6y9ZwBqNtg_RKW_HzB72bd4KQ/edit#)。

- CloudEvents仕様における[属性のバージョニング](https://github.com/cloudevents/spec/blob/v1.0/primer.md#versioning-of-attributes)と[dataschema](https://github.com/cloudevents/spec/blob/v1.0/spec.md#dataschema)フィールド。

- Splunkの[sourcetype](https://docs.splunk.com/Documentation/Splunk/8.1.3/Data/Whysourcetypesmatter)フィールド。

## 付録A. スキーマファイルの例 {#appendix-a-example-schema-file}

```yaml
# Defines the file format. MUST be set to 1.0.0.
file_format: 1.0.0

# The Schema URL that this file is published at. The version number in the URL
# MUST match the highest version number in the "versions" section below.
# Note: the schema version number in the URL is not related in any way to
# the file_format setting above.
schema_url: https://opentelemetry.io/schemas/1.1.0

# Definitions for each schema version in this family.
# Note: the ordering of versions is defined according to semver
# version number ordering rules.
versions:
  1.1.0:
    # Definitions for version 1.1.0.
    all:
      # Definitions that apply to all data types.
      changes:
        # Transformations to apply when converting from version 1.0.0 to 1.1.0.
        - rename_attributes:
            # map of key/values. The keys are the old attribute name used
            # the previous version, the values are the new attribute name
            # starting from this version.
            # Rename k8s.* to kubernetes.*
            k8s.cluster.name: kubernetes.cluster.name
            k8s.namespace.name: kubernetes.namespace.name
            k8s.node.name: kubernetes.node.name
            k8s.node.uid: kubernetes.node.uid
            k8s.pod.name: kubernetes.pod.name
            k8s.pod.uid: kubernetes.pod.uid
            k8s.container.name: kubernetes.container.name
            k8s.replicaset.name: kubernetes.replicaset.name
            k8s.replicaset.uid: kubernetes.replicaset.uid
            k8s.cronjob.name: kubernetes.cronjob.name
            k8s.cronjob.uid: kubernetes.cronjob.uid
            k8s.job.name: kubernetes.job.name
            k8s.job.uid: kubernetes.job.uid
            k8s.statefulset.name: kubernetes.statefulset.name
            k8s.statefulset.uid: kubernetes.statefulset.uid
            k8s.daemonset.name: kubernetes.daemonset.name
            k8s.daemonset.uid: kubernetes.daemonset.uid
            k8s.deployment.name: kubernetes.deployment.name
            k8s.deployment.uid: kubernetes.deployment.uid

    resources:
      # Definitions that apply to Resource data type.
      changes:
        - rename_attributes:
            telemetry.auto.version: telemetry.auto_instr.version

    spans:
      # Definitions that apply to Span data type.
      changes:
        - rename_attributes:
            attribute_map:
              # map of key/values. The keys are the old attribute name used
              # in the previous version, the values are the new attribute name
              # starting from this version.
              peer.service: peer.service.name

    span_events:
      # Definitions that apply to Span Event data type.
      changes:
        - rename_events:
            # The keys are old event name used in the previous version, the
            # values are the new event name starting from this version.
            name_map: {stacktrace: stack_trace}

        - rename_attributes:
            attribute_map:
              peer.service: peer.service.name
            apply_to_events:
              # Optional event names to apply to. If empty applies to all events.
              - exception.stack_trace

    metrics:
      # Definitions that apply to Metric data type.
      changes:
        - rename_metrics:
            # map of key/values. The keys are the old metric name used
            # in the previous version, the values are the new metric name
            # starting from this version.
            container.cpu.usage.total: cpu.usage.total
            container.memory.usage.max: memory.usage.max

        - rename_attributes:
            attribute_map:
              status: state
            apply_to_metrics:
              # Optional. If it is missing the transformation is applied
              # to all metrics. If it is present the transformation is applied
              # only to the metrics with the name that is found in the sequence
              # specified below.
              - system.cpu.utilization
              - system.memory.usage
              - system.memory.utilization
              - system.paging.usage

    logs:
      # Definitions that apply to LogRecord data type.
      changes:
        - rename_attributes:
            attribute_map:
              process.executable_name: process.executable.name

  1.0.0:
    # First version of this schema family.
```

