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


# OTEP-0049: メトリクス`LabelSet`仕様

メトリクスAPIにおいて、あらかじめ定義されたラベルの集合へのハンドルとなる、第一級の `LabelSet` API型を導入します。

## 動機 {#motivation}

ラベルとは、OpenTelemetryのメトリクスAPIで使用されるキーバリューペアを指す用語です。
メトリクスAPIにおけるラベルの扱いは、さまざまなエクスポート戦略にわたるパフォーマンスにとって特に重要です。

ラベルのシリアライズは、メトリクスイベントを処理する際に最もコストのかかる作業の一つであることがよくあります。
`LabelSet` を一度作成し、それを何度も再利用することで、多数のイベントを処理する全体的なコストを大幅に削減できます。

メトリクスAPIは、Handle規約、Direct規約、Batch規約という3つの呼び出し規約をサポートしています。
これらの規約はいずれも、`LabelSet` を再利用することで恩恵を受けます。
なぜなら、SDKが呼び出しのたびにラベルの集合を処理するのではなく、一度だけ処理すればよくなるからです。
同じラベルを使って複数のハンドルが作成される場合や、同じラベルを使って複数の計装器が直接呼び出される場合、あるいは同じラベルを使って複数のバッチのメトリクスイベントが記録される場合には常に、`LabelSet` を再利用することでSDKがパフォーマンスを改善できるようになります。

## 解説 {#explanation}

現在は `{ Key: Value, ... }` の形式でラベルを受け取っているメトリクス計装器APIは、明示的な `LabelSet` を受け取るように更新されます。
`Meter.Labels()` APIメソッドは、APIから `LabelSet` を取得することをサポートしており、プログラマーがあらかじめ定義されたラベルの集合を取得できるようにします。
以下に `LabelSet` の再利用の例をいくつか示します。
2つの計装器があるとします。

```golang
var (
    cumulative = metric.NewFloat64Cumulative("my_counter")
    gauge      = metric.NewFloat64Gauge("my_gauge")
)
```

`LabelSet` を使って複数のハンドルを構築します。

```golang
var (
    labels  = meter.Labels({ "required_key1": value1, "required_key2": value2 })
    chandle = cumulative.GetHandle(labels)
    ghandle = gauge.GetHandle(labels)
)
for ... {
   // ...
   chandle.Add(...)
   ghandle.Set(...)
}
```

`LabelSet` を使って複数のDirect呼び出しを行います。

```golang
labels := meter.Labels({ "required_key1": value1, "required_key2": value2 })
cumulative.Add(quantity, labels)
gauge.Set(quantity, labels)
```

もちろん、`Meter.RecordBatch()` を繰り返し呼び出す場合にも、同様に `LabelSet` を再利用できます。

### 順序付き`LabelSet`オプション {#ordered-labelset-option}

言語レベルの判断として、APIは _順序付き_ LabelSet構築をサポートしてもかまいません。
これは、あらかじめ定義された順序付きラベルキーの集合を定義することで、値を順番に渡せるようにするものです。
これにより、`LabelSet` を構築する際により高速なコードパスを利用できるようになります。
例えば、

```golang

var rpcLabelKeys = meter.OrderedLabelKeys("a", "b", "c")

for _, input := range stream {
    labels := rpcLabelKeys.Values(1, 2, 3)  // a=1, b=2, c=3

    // ...
}
```

この機能は言語ごとに任意で採用できる機能として規定されています。
なぜなら、その安全性、ひいてはモニタリングへの入力としての価値は、ソース言語における型チェックの有無に依存するからです。
順序を持たないラベル（すなわち、束縛されたキーと値のリスト）を `Meter.Labels(...)` に渡す方法は、より安全な代替手段と考えられます。

### 「名前付き」Meterとの相互作用 {#interaction-with-named-meters}

LabelSetの値は、同じMeterプロバイダーに由来する任意の名前付きMeterで使用できます。
つまり、ある名前付きMeterを通じて取得したLabelSetは、同じMeterプロバイダー由来の任意のMeterで使用できます。

## 内部の詳細 {#internal-details}

LabelSetの最適化を活用しない、あるいは活用できないメトリクスSDKであっても、これらのAPIをサポートすることに特に大きな負担はありません。
単にラベルのリストを保持するだけの `LabelSet` の実装を提供することは、些細なことです。
パフォーマンスが重視されるアプリケーションではこれでは受け入れられないかもしれませんが、これは今日の多くのメトリクスAPIおよび診断APIにおける一般的なケースです。

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

オーバーロードが標準的な利便性として提供されている言語では、メトリクスAPIは `Meter.Labels()` の呼び出しを省略できる代替形式を提供することを選んでもかまいません。
例えば、

```
instrument.GetHandle({ Key: Value, ... })
```

これに対して、以下のような形式です。

```
instrument.GetHandle(meter.Labels({ Key: Value, ... }))
```

`LabelSet` と、既存のメトリクスライブラリにおける類似の概念との重要な違いは、それが _書き込み専用_ の構造体であるという点です。
`LabelSet` は、開発者がメトリクスのラベルを入力できるようにしますが、それを読み戻すことはできません。
これにより、SDKが不要なメモリへの参照を保持せざるを得なくなることを回避します。

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

既存のメトリクスAPIの中には、この概念をサポートしているものがあります。
例えば、[GoのTallyメトリクスAPI](https://pkg.go.dev/github.com/uber-go/tally#Scope) における `Scope` を参照してください。

ライブラリの中には、`LabelSet` をさらに一歩進めているものもあります。
将来的には、ラベルの集合に追加のラベルを拡張するためのメソッドを `LabelSet` APIに追加するかもしれません。
例えば、

```
serviceLabels := meter.Labels({ "k1": "v1", "k2": "v2" })
// ...
requestLabels := serviceLabels.With({ "k3": "v3", "k4": "v4" })
```

