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 は、MeasureMetricValue(整数または浮動小数点数)のペアとして定義し直されます。

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のメトリクス部分を表します。

メトリクス計装器には、CumulativeMetricGaugeMetricMeasureMetric の3種類があります。

メトリクス計装器は Meter APIを通じて構築します。 計装器を構築すると、自動的にSDKに登録されます。 メトリクス計装器に共通する属性は次のとおりです。

フィールド説明
Name文字列。
KindCumulative、Gauge、Measureのいずれか。
Recommended Keysデフォルトの集約キー。
Unit記録される計測値の単位。
Descriptionこのメトリクスに関する情報。

これらのフィールドに関する、フォーマットや一意性の要件を含む詳細については仕様を参照してください。 新しいメトリクスを定義するには、Meter APIのメソッド(たとえば NewCumulativeMetricNewGaugeMetricNewMeasureMetric のような名前のメソッド)のいずれかを使用します。

メトリクス計装器のHandleは、メトリクス計装器とあらかじめ定義されたラベルの集合を組み合わせたものです。 Handleは、特定のラベル値を指定してメトリクス計装器に対して言語固有のAPIメソッド(たとえば GetHandle)を呼び出すことで取得します。 Handleは、その種別に応じて Set()Add()Record() によるメトリクスの記録に使用できます。

Selecting Metric Kind

OpenTelemetryにおけるAPIと実装の分離により、実装はメトリクスAPI呼び出しに応じて 何でも 自由に行えることがわかっています。 上で定義した低レベルの解釈によれば、すべてのメトリクスイベントは同じ構造的表現を持ち、その論理的な解釈のみがメトリクス定義に応じて異なります。 したがって、私たちは次の2つの主要な観点に基づいてメトリクス種別を選択します。

  1. デフォルトの実装の挙動はどうあるべきか。特に設定しない限り、実装はこのメトリクス変数をどのように扱うべきか。
  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メトリクスは、計測値の分布を表します。 この種別は、イベントの件数やレートに意味があり、かつ以下のいずれかに該当する場合に使用するべきです。

  1. 件数(レート)に加えて合計値が関心事である
  2. 分位数の情報が関心事である

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の更新をアトミックに扱ってもかまいません(必須ではありません)。

すべてのメトリクス計装器に対するバッチ記録に反対する議論は次のとおりです。

  • RecordBatchRecord という名前は、それがMeasureメトリクスに適用されることを示唆しています。これは、Measureメトリクスがメトリクス計装器の中でもっとも汎用的であることに由来します。

Issues addressed

Rawメトリクスとその他のメトリクス/計測値の区別が不明瞭である

アロケーションを削減するためにMeasurementクラスを廃止する

3種類の追加のメトリクス種別を実装する