OTEP-0003: 事前集計メトリクスAPIと生メトリクスAPIの統合
Foreword
2019年8月21日にワーキンググループが招集され、2つのメトリクスRFC(0003と0004)およびそれに関連するいくつかの懸念事項について議論しました。 本ドキュメントは、このワーキングセッションで合意された関連する更新を反映して改訂されています。 詳細はミーティングノートを参照してください。
Overview
Record APIメソッドをサポートする Measure 種別のメトリクスオブジェクトを導入します。
既存の Gauge および Cumulative メトリクスと同様に、新しい Measure メトリクスもあらかじめ定義されたラベルをサポートします。
複数のメトリクス観測値を同時に記録するための新しい RecordBatch 計測APIも導入します。
Terminology
本RFCは、OpenTelemetryのメトリクス仕様における「Measure」の使われ方を変更します。 変更前は、「Measure」は一連の生の計測値の名前でした。 変更後は、「Measure」は一連の生の計測値を記録するために使用されるメトリクスオブジェクトの種別を指します。
本ドキュメントは提案が書かれた後、将来にわたって読まれるものであるため、「現在」という単語を使うと混乱を招きます。 本ドキュメントでは、これらの変更が行われる前に現在であった状態を指す用語として「以前」を使用します。
以前の仕様では、あらかじめ定義されたラベルの集合にバインドされた計装器を表すために TimeSeries という用語を使用していました。
本ドキュメントでは、ラベルがバインドされた計装器を表す用語として「Handle」を使用します。
将来のOTEPでは、これは再び「Bound instrument」に変更される予定です。
本ドキュメントでは、バインドされた計装器を指す用語として一貫して「Handle」を使用します。
Motivation
GaugeおよびCumulativeのための以前の Metric.GetOrCreateTimeSeries APIでは、呼び出し元は、あらかじめ定義された特定のラベル値の集合を使ってメトリクスを繰り返し記録するための TimeSeries ハンドルを取得していました。
これにより、実装はポインタや高速なテーブルルックアップを使って集約サマリーの「エントリ」を計算できるため、事前集計されたメトリクスをエクスポートする際の重要な最適化が可能になります。
この効率化の恩恵を受けるには、集約キーがあらかじめ定義されたラベルの部分集合である必要があります。
長寿命のオブジェクトとそれに関連するメトリクスを持つアプリケーションプログラムは、呼び出し箇所ごとにラベル値を計算するのではなく、オブジェクトごとに一度だけ(たとえばコンストラクタの中で)ラベル値を計算することで、あらかじめ定義されたラベルを活用できます。 このように、あらかじめ定義されたラベルの使用は、APIの使いやすさを向上させるだけでなく、実装における重要な最適化も可能にします。
以前の生の統計APIは、あらかじめ定義されたラベルのサポートを規定していませんでした。
本RFCは、この生の統計APIを、あらかじめ定義されたラベルの明示的なサポートを備えた、新しい汎用のメトリクス種別である MeasureMetric に置き換えます。
MeasureMetric は、以前の生の統計APIと同様に、一般に個々の計測値を記録することを意図しています。
以前の生の統計APIは、共通のラベルセットを使用する相互依存の計測値に対して、全部を記録するか何も記録しないかのオールオアナッシングな記録をサポートしていました。
本RFCは、1回のAPI呼び出しで複数の計測値をまとめて記録することをサポートする RecordBatch APIを導入します。
ここで Measurement は、MeasureMetric と Value(整数または浮動小数点数)のペアとして定義し直されます。
Explanation
MeasureMetric の一般的な用途は、以前の生の統計APIと同様に、構造化された数値のイベントデータに関するレートと分布の情報を報告することです。
Measureメトリクスは、メトリクスの中でもっとも汎用的なものです。
非公式に言うと、個々のメトリクスイベントは、1つの主要なkey=value(メトリクス名と数値)と、任意の数の副次的なkey=value(ラベル、リソース、コンテキスト)で表される論理フォーマットを持ちます。
metric_name=_number_
pre_defined1=_any_value_
pre_defined2=_any_value_
...
resource1=_any_value_
resource2=_any_value_
...
context_tag1=_any_value_
context_tag2=_any_value_
...
ここで、「pre_defined」キーはメトリクスハンドルに取り込まれるもの、「resource」キーはSDKの初期化時に設定されるもの、「context_tag」キーはコンテキストを通じて伝播されるものです。
この形式のイベントは、cumulative、gauge、measureのいずれの種類のメトリクスであっても、名前付きメトリクスに対する単一の更新を論理的に捉えることができます。 この論理構造は、3種類のメトリクスすべてに共通する、任意のメトリクスイベントの 低レベルエンコーディング を定義します。 SDKはこれらのイベントのストリームを単純にエンコードするだけでよく、消費者側はメトリクス定義へのアクセスさえあれば、各メトリクス種別に規定されたセマンティクスに従ってこれらのイベントを解釈できるはずです。
Metrics API concepts
Meter インターフェースは、OpenTelemetry APIのメトリクス部分を表します。
メトリクス計装器には、CumulativeMetric、GaugeMetric、MeasureMetric の3種類があります。
メトリクス計装器は Meter APIを通じて構築します。
計装器を構築すると、自動的にSDKに登録されます。
メトリクス計装器に共通する属性は次のとおりです。
| フィールド | 説明 |
|---|---|
| Name | 文字列。 |
| Kind | Cumulative、Gauge、Measureのいずれか。 |
| Recommended Keys | デフォルトの集約キー。 |
| Unit | 記録される計測値の単位。 |
| Description | このメトリクスに関する情報。 |
これらのフィールドに関する、フォーマットや一意性の要件を含む詳細については仕様を参照してください。
新しいメトリクスを定義するには、Meter APIのメソッド(たとえば NewCumulativeMetric、NewGaugeMetric、NewMeasureMetric のような名前のメソッド)のいずれかを使用します。
メトリクス計装器のHandleは、メトリクス計装器とあらかじめ定義されたラベルの集合を組み合わせたものです。
Handleは、特定のラベル値を指定してメトリクス計装器に対して言語固有のAPIメソッド(たとえば GetHandle)を呼び出すことで取得します。
Handleは、その種別に応じて Set()、Add()、Record() によるメトリクスの記録に使用できます。
Selecting Metric Kind
OpenTelemetryにおけるAPIと実装の分離により、実装はメトリクスAPI呼び出しに応じて 何でも 自由に行えることがわかっています。 上で定義した低レベルの解釈によれば、すべてのメトリクスイベントは同じ構造的表現を持ち、その論理的な解釈のみがメトリクス定義に応じて異なります。 したがって、私たちは次の2つの主要な観点に基づいてメトリクス種別を選択します。
- デフォルトの実装の挙動はどうあるべきか。特に設定しない限り、実装はこのメトリクス変数をどのように扱うべきか。
- プログラムのソースコードはどのように読めるか。各メトリクスは異なる動詞を使用し、それが意味を伝え、デフォルトの挙動を説明する助けとなる。Cumulativeは
Add()メソッドを持つ。GaugeはSet()メソッドを持つ。MeasureはRecord()メソッドを持つ。
アプリケーションに適したメトリクス種別を選ぶ際にユーザーの助けとなるよう、与えられたデータを報告する主目的について、次の問いを検討します。 ここでは「主要な関心事」という言葉を、システムの挙動を理解する上でほぼ確実に有用な情報という意味で使用します。 次の問いを検討してください。
- その計測値は何かの量を表しているか。また、それは非負であるか。
- 合計値が主要な関心事であるか。
- イベント数が主要な関心事であるか。
- 分布(p50、p99など)が主要な関心事であるか。
仕様は、以下のガイダンスに沿って更新される予定です。
Cumulative metric
おそらくもっとも一般的なメトリクス種別であり、Cumulativeメトリクスのイベントは合計値の計算を表します。
値が量であり、合計値が主要な関心事であり、イベント数や分布が主要な関心事ではない場合にこの種別を選択してください。
Cumulativeメトリクスの値を増加(または減少)させるには Add() メソッドを呼び出します。
対象となる量が常に非負である場合、それは合計値が単調であることを意味します。
これは一般的なケースであり、Monotonic(true) として表されます。この場合、Cumulativeの合計値は増加する一方であり、これらのメトリクス計装器はレートの計算に役立ちます。
このため、Cumulativeメトリクスには、一般的ではないケースとして負の入力を許容することを宣言する Monotonic(false) オプションがあります。
SDKは、単調なCumulativeメトリクスに対する負の入力を拒否するべきですが、必須ではありません。
Cumulativeメトリクスについて、OpenTelemetryのデフォルト実装は、ある時間間隔にわたるイベント値の合計をエクスポートします。
Gauge metric
Gaugeメトリクスは、明示的な計装によって Set() されるか、コールバックを通じて観測される、あらかじめ計算された値を表します。
一般に、計測間隔が任意であるためにメトリクスを合計値やレートとして表現できない場合に、この種別を使用するべきです。
計測値が計算済みの値であり、合計値やイベント数が関心事ではない場合にこの種別を使用してください。
Gauge種別のメトリクスのみが、gaugeの Observer コールバック(オプションとして、0008-metric-observer.md を参照)を介したメトリクスの観測をサポートします。
意味論的には、gaugeを明示的に設定することとコールバックを通じて観測することの間には重要な違いがあります。
gaugeを明示的に設定する場合、Set() の呼び出しは暗黙または明示のコンテキストの中で発生します。
実装は、たとえば明示的な Set() イベントをコンテキストに関連付けることが自由にできます。
コールバックを通じてgaugeメトリクスを観測する場合、そのイベントに関連付けられるコンテキストはありません。
特殊なケースとして、既存のメトリクス基盤と Observer パターンをサポートするために、gaugeメトリクスは Monotonic(true) オプションを使って、あらかじめ計算された単調な合計値として宣言できます。
この場合、それはレートを定義するために使用できます。
初期値は0であると仮定されます。
SDKは、単調なgaugeに対する降順の更新を拒否するべきですが、必須ではありません。
Gaugeメトリクスについて、OpenTelemetryのデフォルト実装は、明示的に Set() された最後の値、またはコールバックを使用している場合は Observer からの現在の値をエクスポートします。
Measure metric
Measureメトリクスは、計測値の分布を表します。 この種別は、イベントの件数やレートに意味があり、かつ以下のいずれかに該当する場合に使用するべきです。
- 件数(レート)に加えて合計値が関心事である
- 分位数の情報が関心事である
Measureメトリクスのイベントの重要な特性は、分位数の計算や(ヒストグラムなどによる)分布の要約が高コストになり得るということです。 この作業に対して実装がさまざまな能力とアルゴリズムを持つだけでなく、ユーザーはMeasureメトリクスの集約の品質とコストを制御したいと考えるかもしれません。
Cumulativeメトリクスと同様に、非負のMeasureはレート計算をサポートするため重要なケースです。
Measureメトリクスは、入力が非負である場合に Absolute(true) と表現されます。
オプションとして、Measureメトリクスは正負両方の値をサポートするために Absolute(false) として宣言することもできます。
SDKは、Absoluteなmeasureに対する負の計測値を拒否するべきですが、必須ではありません。
Option to disable metrics by default
メトリクス計装器はデフォルトで有効になっており、これはSDKが設定なしにこの計装器のメトリクスデータをエクスポートすることを意味します。
メトリクス計装器は Disabled オプションをサポートしており、これによって、コストを制御するために必要に応じて設定できる(たとえば「views」APIを使用するなど)冗長な情報源としてマークすることができます。
Kind-specific option summary
メトリクス計装器の種別ごとのオプションプロパティは次のとおりです。
| プロパティ | 説明 | メトリクス種別 |
|---|---|---|
| Monotonic(true) | 非負の値のみを受け入れるCumulativeであることを示す | Cumulative(デフォルト) |
| 0から始まる昇順の値のシーケンスをサポートするgaugeであることを示す | Gauge | |
| Monotonic(false) | 正負両方の値を受け入れるCumulativeであることを示す | Cumulative |
| 単調なCumulativeの値を表すgaugeであることを示す | Gauge(デフォルト) | |
| Absolute(true) | 非負の値を受け入れるMeasureであることを示す | Measure(デフォルト) |
| Absolute(false) | 正負両方の値を受け入れるMeasureであることを示す | Measure |
RecordBatch API
アプリケーションは、値が互いに関連しているため、あるいはオーバーヘッドを削減するために、1回のAPI呼び出しで複数のメトリクス計装器に対して操作を行いたいことがあります。 RecordBatchは、指定された値を使ってバッチ内の各計装器を論理的に更新します。 単一のラベルセットがバッチ全体に適用されます。
単一の計測値は次のように定義されます。
- Instrument: measure計装器(Handleではない)
- Value: 記録される浮動小数点数または整数のデータ
このバッチ計測APIは、言語固有のメソッド名(たとえば RecordBatch)を使用します。
計測値のバッチ全体は、(暗黙または明示的な)1つのコンテキストの中で行われます。
Prior art and alternatives
Prometheusは、特定の必須キーの集合に対してあらかじめ定義されたラベルをサポートする、vectorメトリクスという概念をサポートしています。
vectorメトリクスAPIは、OpenTelemetryにおける GetHandle と同様に、ラベルをメトリクスハンドルに関連付けるための WithLabelValues のようなさまざまなメソッドをサポートしています。
本提案と同様に、Prometheusはすべてのメトリクス種別に対してvector APIをサポートしています。
Open questions
GetHandle argument ordering
GetHandle にあらかじめ定義されたラベル値を渡す方法として、引数の順序を用いる案が提案されています。
この場合、引数のリストはパラメータのリストと厳密に一致する必要があり、一致しない場合、一般には実行時にしか発覚しないか、まったく発覚しません。
このモデルはより大きな最適化の可能性を持ちますが、代替案よりも誤用されやすいという側面があります。
代替のアプローチは、値の順序付きリストの代わりに、常に label:value のペアを GetOrCreateTimeseries に渡す方法です。
RecordBatch argument ordering
上記と同様の議論は、提案されている RecordBatch メソッドについても当てはまります。
これは、メトリクスの順序付きリストを使って宣言することができ、その場合 Record APIは数値の順序付きリストのみを受け取ります。
あるいは、誤用されにくい代替案として、RecordBatch APIを metric:number のペアのリストで宣言する方法もあります。
Eliminate GetDefaultHandle()
デフォルトのHandleを取得する仕組みの代わりに、一部の言語では、この場合には単純にメトリクス計装器を直接操作することを好むかもしれません。
OpenTelemetryは GetDefaultHandle を廃止し、代わりにcumulative、gauge、measureの各メトリクス計装器が、同じ解釈で Add()、Set()、Record() を実装するよう規定すべきでしょうか。
GetDefaultHandle() を廃止する場合、SDKは独自にメトリクス計装器からデフォルトのHandleへのマップを保持してもかまいません。
RecordBatch support for all metrics
8月21日のワーキングセッションでは、RecordBatch を同時に複数のMeasureメトリクスを記録する用途に限定し、CumulativeとGaugeをバッチ記録から除外することで合意しました。
すべてのメトリクス計装器に対してバッチ記録をサポートすることを支持する議論もあります。
- 原子性(すなわちオールオアナッシングの性質)がバッチ報告の理由であるなら、すべてのメトリクス計装器をAPIに含めることには意味があります。
- メトリクスイベントのフォワーダーとして機能するSDKにとって、CumulativeとGaugeに対する
RecordBatchのサポートは自然なものになります。Add()およびSet()メソッドの自然な実装は、単一のイベントを伴うRecordBatchになるでしょう。 - 同様に、(フォワーダーではなく)アグリゲーターとして機能するSDKにとっても、
Add()およびSet()のAPIをHandle固有のAdd()およびSet()メソッドにリダイレクトするのは簡単です。その一方で、SDKは実装として、これらのCumulativeやGaugeの更新をアトミックに扱ってもかまいません(必須ではありません)。
すべてのメトリクス計装器に対するバッチ記録に反対する議論は次のとおりです。
RecordBatchのRecordという名前は、それがMeasureメトリクスに適用されることを示唆しています。これは、Measureメトリクスがメトリクス計装器の中でもっとも汎用的であることに由来します。
Issues addressed
Rawメトリクスとその他のメトリクス/計測値の区別が不明瞭である