OTEP-0098: メトリクスインストゥルメントの解説

OTEP 88 で理論化された標準メトリクスインストゥルメントの最終的な名称を提案・解説し、関連する混乱に対応します。

動機

OTEP 88 は、インストゥルメントの2つの基本カテゴリー「同期」と「非同期」を、抽象的な意味で「Measure」と「Observer」と名付けたメトリクスインストゥルメントの論理構造を導入しました。 この提案は4種類の「refinement(洗練)」を特定し、可能な インストゥルメントの空間を描き出しましたが、実際に標準へ含めるものについては提案していませんでした。

OTEP 93 は、もっとも必要かつ有用なインストゥルメントrefinementの組み合わせである6つの標準インストゥルメントと、タイミング計測を記録するために使われる特殊なケースを1つ加えたリストを提案しました。 OTEP 93は、より一貫性のある命名アプローチが見出されたためマージされずにクローズされました。 OTEP 96 はさらなる議論の末、本提案に有利な形でクローズされました。

本提案は、「Measure」と「Observer」という用語に関連する根本的な混乱に対応することを目指し、標準インストゥルメントの命名提案を最終化するものです。

  1. OTEP 88 は、同期・非同期インストゥルメントの命名に現在使われている用語「Measure」と「Observer」が 抽象的な 用語になると規定しています。 また、refinementを持つインストゥルメントを議論する際に「Measure-like」「Observer-like」というフレーズも使用していました。 本提案は、インストゥルメントの種類を説明する際には、一般に「Sync」「Async」と略される形容詞を優先して使うべきであると述べます。 「Measure-like」はインストゥルメントが同期的であることを意味します。 「Observer-like」はインストゥルメントが非同期的であることを意味します。
  2. OTEP 88 で提示された仮のインストゥルメント命名スキームには一貫性がありません。 「Counter」と「Observer」は「職業的に関連する人」という意味で使われる名詞接尾辞「-er」で終わるのに対し、「Measure」という用語はこのパターンに当てはまりません。 本提案は、関連する関数名(動詞)が Record() として規定されているため、抽象的な用語「Measure」を「Recorder」に置き換えることを提案します。

本提案はまた、それぞれの標準インストゥルメントのデフォルト集約についての現行の仕様と、その根拠を改めて述べます。

解説

以下の表は、この一連の提案から導かれる最終的な標準インストゥルメントの提案をまとめたものです。 各列の詳細は以下で説明します。

既存の名前標準名インストゥルメントの種類関数名入力の時間的性質デフォルト集約レートのサポート(Monotonic)備考
CounterCounterSyncAdd()DeltaSumYesリクエスト単位、monotonicなsumの一部
UpDownCounterSyncAdd()DeltaSumNoリクエスト単位、non-monotonicなsumの一部
MeasureValueRecorderSyncRecord()InstantaneousMinMaxSumCountNoリクエスト単位、非加算的な計測全般
SumObserverAsyncObserve()CumulativeSumYes間隔単位、monotonicなsumを報告
UpDownSumObserverAsyncObserve()CumulativeSumNo間隔単位、non-monotonicなsumを報告
ObserverValueObserverAsyncObserve()InstantaneousMinMaxSumCountNo間隔単位、非加算的な計測全般

OTEP 88 では仮に10種類のインストゥルメントが議論されましたが、本提案には3つの同期インストゥルメントと3つの非同期インストゥルメントの合計6種類があります。 理にかなっており論理的だと考えてはいるものの、2つのカテゴリー、すなわち同期のcumulativeインストゥルメントと非同期のdeltaインストゥルメントは本提案から除外されています。

同期のcumulativeインストゥルメントは、OpenTelemetryライブラリのパフォーマンスガイドラインに基づき標準から除外されています。 実行時にcumulative値を正しく報告するには、ある程度の順序依存性、すなわち同期が必要ですが、これはOpenTelemetry API自体が許容しないものです。 仮に、2つのアクターが同期的にsumを変更し、それを同期のcumulativeメトリクスイベントを使って捕捉しようとする例を考えると、OpenTelemetryライブラリはそれらの計測が順序どおりに処理されることを保証しなければならなくなります。 ライブラリのガイドラインはこのレベルの同期をサポートしていません。 私たちは計装のためにブロックすることはできないので、同期のcumulativeインストゥルメントをサポートしません。

非同期のdeltaインストゥルメントは、それを動機づける実例がないという理由で標準から除外されていますが、非同期コールバックをステートレスに保ちたいという意図としても正当化できます。 Observerがdeltaを計算するにはメモリを持つ必要があります。 非同期コードにとってはcumulative値を報告する方が単純です。

合計6つのインストゥルメントがある中で、歴史的なMetrics APIの用語である Gauge がこの仕様にどう変換されるのか気になる人もいるでしょう。 Metrics APIの用語における Gauge は、Counter を除くこれらのインストゥルメントのユースケースすべてをカバーし得ます。 OTEP 88 で定義されているように、OpenTelemetry Metrics APIは 単一目的のインストゥルメント を要求することでこれらのユースケースを明確に区別します。 インストゥルメントの選択は、デフォルトの解釈、標準的な集約を暗黙のうちに示し、オブザーバビリティシステムにおいてメトリクスデータをどのように扱うべきかを、すぐに使える形で示唆します。 Gauge の用途は、どのような値が捕捉されるのか、そしてその計測が同期的に行われるかどうかに応じて、さまざまなOpenTelemetryメトリクスインストゥルメントに変換されます。

命名スキームをまとめると次のとおりです。

  • 合計されて総量になり、その合計値に主として関心があるような量を計測した場合は、加算的なインストゥルメントのいずれかを使用します。
    • 同期的かつmonotonicなら、非負の値とともに Counter を使用します。
    • 同期的かつnon-monotonicなら、任意の値とともに UpDownCounter を使用します。
    • 非同期でcumulativeかつmonotonicなsumを計測するなら、SumObserver を使用します。
    • 非同期でcumulativeかつ任意のsumを計測するなら、UpDownSumObserver を使用します。
  • 計測が非加算的である場合、または加算的であっても分布に関心がある場合は、instantaneousなインストゥルメントを使用します。
    • 同期的なら、分布の一部となる値を記録するために ValueRecorder を使用します。
    • 非同期なら、収集間隔の終わり近くで単一の計測を記録するために ValueObserver を使用します。

同期インストゥルメントと非同期インストゥルメント

同期インストゥルメントはリクエストコンテキストの中で呼び出されるため、関連するトレーシングコンテキストや分散相関値を持ち得ます。 1つの同期インストゥルメントに対して、ある収集間隔の中で複数のメトリクスイベントが発生することがあります。 なお、同期インストゥルメントはバックグラウンド計算のように、リクエストコンテキストの外で呼び出されることもあります。 このようなシナリオでは、Contextを単に空であると見なしてかまいません。

非同期インストゥルメントはコールバックによって収集間隔ごとに1回報告され、リクエストコンテキストを持ちません。 1つの期間につき、区別されるラベルセットごとに1つの値のみを報告することが許されます。 アプリケーションが1回のコールバックの中で、1つの収集間隔について複数の値を観測した場合、最後の値が「勝ち」ます。

時間的性質

計測は、時間との関係の観点から記述することができます。 注意: この用語は論理的に当てはまり、本OTEP全体を通じて使われていますが、Metrics SIGミーティング(2020年4月30日)での議論により、Metrics APIの文書化ではこの用語の使用を除外することにしました。 ここでの用語の説明は[プロトコルで使われている用語]と整合していますが、私たちはこれらの形容詞を、インストゥルメントの性質ではなく集約の性質を記述するために使うことを優先します(本ドキュメントは以降もこれらの用語を自由に使い続けますが)。 API仕様では、この区別を「additive synchronous(加算的な同期)」と「additive asynchronous(加算的な非同期)」の対比を用いて記述します。

Delta計測とは、sumに対する変化を計測するものです。 Deltaインストゥルメントが選ばれるのは、通常、プログラムが自分自身でsumを計算する必要がなく、変化量を計測できる場合です。 このようなケースでは、ユーザーがcumulative値を報告するには余計な状態が必要になるため、deltaを報告する方が自然です。

Cumulative計測とは、sumの現在の値を報告するものです。 Cumulativeインストゥルメントが選ばれるのは、通常、プログラムが自身の目的のためにsumを保持している場合、あるいはsumの変化が計装されていない場合です。 このようなケースでは、ユーザーがdelta値を報告するには余計な状態が必要になるため、cumulative値を報告する方が自然です。

Instantaneous計測とは、非加算的な計測、すなわちsumを計算することが自然ではない計測を報告するものです。 Instantaneousインストゥルメントは通常、sumだけでなく値の分布に関心がある場合に選ばれます。

本提案で使われる「Delta」「Cumulative」「Instantaneous」という用語は、Metrics APIに渡される計測値を指します。 (加算的な)Deltaの時間的性質を持つインストゥルメントへの引数は、sumに対する変化量です。 (加算的な)Cumulativeの時間的性質を持つインストゥルメントへの引数は、それ自体がsumです。 Instantaneousの時間的性質を持つインストゥルメントへの引数は、単なる値です。 SDK仕様では、計測が集約されエクスポート用に変換される際に、これらの用語が同じ意味で再び使われ、集約結果を記述します。

関数名

同期のdeltaインストゥルメントは Add() 関数をサポートします。これは、sumに加算するものであり、cumulativeではないことを示します。

同期のinstantaneousインストゥルメントは Record() 関数をサポートします。これは、sumだけでなく個々のイベントを捕捉することを示します。

非同期インストゥルメントはすべて Observe() 関数をサポートします。これは、計測間隔ごとに1つの値のみを捕捉することを示します。

レートのサポート

デフォルトの実装では、CounterおよびSumObserverインストゥルメントに対してレート集約がサポートされます。

加算的インストゥルメントの UpDown- 形式は、状態の増加と減少が互いに打ち消し合う可能性があるため、レートの集約には適していません。

非加算的インストゥルメントもsumを導出するために使用でき、値が非負であればレート集約が可能です。 標準には、非負のrefinementを持つ非加算的インストゥルメントは存在しません。

デフォルトの集約

加算的インストゥルメントは定義上sumにのみ関心があるため、デフォルトで Sum 集約を使用します。

Instantaneousインストゥルメントは、値の分布を安価に要約する方法である MinMaxSumCount 集約をデフォルトで使用します。

詳細

ここでは、提案された6つのインストゥルメントをそれぞれ個別に議論し、それぞれについて検討された他の名前にも触れます。

Counter

Counter はもっとも一般的な同期インストゥルメントです。 このインストゥルメントはsumを報告するための Add(delta) 関数をサポートし、非負のdeltaに制限されます。 デフォルトの集約は、DeltaまたはCumulativeの計測種別を持つあらゆる加算的インストゥルメントと同様に Sum です。

Counter の使用例は以下のとおりです。

  • 受信したバイト数を数える
  • 作成されたアカウント数を数える
  • 実行されたチェックポイント数を数える
  • 5xxエラーの数を数える

これらの例のインストゥルメントは、いずれの量のレートを監視するのにも有用でしょう。 このような状況では、通常、sumを維持して報告するよりも、その変化が起きた時点で関連するsumの変化を報告する方が便利です。

検討された他の名前: AdderSumCounter

UpDownCounter

UpDownCounterAdd(delta) が負のdeltaもサポートする点を除けば Counter に似ています。 そのため UpDownCounter はレート集約の計算には有用ではありません。 UpDownCounter はsumを集約しますが、そのsumはnon-monotonicです。 一般に、リクエストコンテキストにおいて、使用されているリソースの量の変化や、増減する任意の量を数えるのに有用です。

UpDownCounter の使用例は以下のとおりです。

  • newdelete を計装してメモリ使用量を数える
  • enqueuedequeue を計装してキューサイズを数える
  • セマフォの updown の操作を数える

これらの例のインストゥルメントは、プロセス群にわたるリソースレベルの監視に有用でしょう。

検討された他の名前: NonMonotonicCounter

ValueRecorder

ValueRecorder は、正負を問わず任意の非加算的な数値を記録するのに有用な、非加算的な同期インストゥルメントです。 ValueRecorder によって捕捉された値は、要約対象の分布に属する個々のイベントとして扱われます。 ValueRecorder は、sumに意味のある形で寄与しない計測を捕捉する場合、または本質的には加算的な数値であっても個々の増分の分布に関心がある場合に選ぶべきです。

ValueRecorder のもっとも一般的な用途の1つは、レイテンシー計測の捕捉です。 処理されたすべてのリクエストのレイテンシーの合計を知る必要はほとんどないという意味で、レイテンシー計測は加算的ではありません。 私たちが ValueRecorder インストゥルメントを使ってレイテンシー計測を捕捉するのは、通常、個々のイベントについて平均、中央値、その他の要約統計を知ることに関心があるためです。

ValueRecorder のデフォルト集約は、最小値と最大値、イベント値のsum、イベントのcountを計算し、入力値のレート、平均、範囲を監視できるようにします。

非加算的な ValueRecorder の使用例は以下のとおりです。

  • あらゆる種類のタイミング情報を捕捉する
  • パイロットが経験する加速度を捕捉する
  • 燃料噴射装置のノズル圧力を捕捉する
  • MIDIキー押下の速度を捕捉する

加算的な ValueRecorder の使用例は、cumulativeまたはdeltaの値である計測を捕捉しますが、sumだけでなく分布に関心がある場合です。

  • リクエストサイズを捕捉する
  • 口座残高を捕捉する
  • キューの長さを捕捉する
  • 木材のボードフィート数を捕捉する

これらの例は、本質的には加算的であっても、CounterUpDownCounter ではなく ValueRecorder を選ぶことが、sum以上のものへの関心を意味することを示しています。 分布についての情報を収集することに関心がないなら、代わりに加算的インストゥルメントの1つを選んでいたはずです。 ValueRecorder を使うことは、オブザーバビリティの設定において重要になりそうな分布に対して理にかなっています。

これらは加算的な計測の使用よりも当然コストが高くなるため、注意して使用してください。

検討された他の名前: DistributionMeasureLastValueRecorderGaugeRecorderDistributionRecorder

SumObserver

SumObserverCounter に対応する非同期インストゥルメントで、monotonicなcountを捕捉するために使われます。 名前に「Sum」が含まれているのは、これがcumulativeなインストゥルメントであることをユーザーに思い出させるためです。 SumObserver は、ゼロから始まり、プロセスのライフタイムを通じて上昇するが決して下がらない値を捕捉するために使用します。

SumObserver の使用例は以下のとおりです。

  • プロセスのuser/system CPU秒を捕捉する
  • キャッシュミス数を捕捉する

SumObserver は、計測のコストが高く、リクエストごとに計算するのが無駄になるような状況に適した選択です。 たとえば、プロセスのCPU使用率を捕捉するにはシステムコールが必要なため、リクエストごとではなく定期的に行うべきです。 SumObserver は、sumを構成する個々のdeltaを計装するのが非実用的または無駄になるような状況にも適した選択です。 たとえば、キャッシュミス数は個々のキャッシュミスイベントのsumではあるものの、Counter を使って各イベントを同期的に捕捉するのはコストが高すぎます。

検討された他の名前: CumulativeObserver

UpDownSumObserver

UpDownSumObserverUpDownCounter に対応する非同期インストゥルメントで、non-monotonicなcountを捕捉するために使われます。 名前に「Sum」が含まれているのは、これがcumulativeなインストゥルメントであることをユーザーに思い出させるためです。 UpDownSumObserver は、ゼロから始まり、プロセスのライフタイムを通じて上昇または下降する値を捕捉するために使用します。

UpDownSumObserver の使用例は以下のとおりです。

  • プロセスのヒープサイズを捕捉する
  • アクティブなシャード数を捕捉する
  • 開始/完了したリクエスト数を捕捉する
  • 現在のキューサイズを捕捉する

同期の Counter に対して SumObserver を選ぶ際に述べたのと同様の考慮事項が、同期の UpDownCounter に対して UpDownSumObserver を選ぶ際にも当てはまります。 計測のコストが高い場合、または対応するdeltaイベントが頻発しすぎて計装するのが非実用的な場合は、UpDownSumObserver を使用してください。

検討された他の名前: UpDownCumulativeObserver

ValueObserver

ValueObserverValueRecorder に対応する非同期インストゥルメントで、コストが高く、かつ/またはリクエスト指向ではない非加算的な計測を捕捉するために使われます。

ValueObserver の使用例は以下のとおりです。

  • CPUファンの速度を捕捉する
  • CPU温度を捕捉する

これらの例は非加算的な計測を使用していることに注意してください。 上記の ValueRecorder のケースでは、リクエストコンテキストにおける同期のcumulative計測(たとえば、あるリクエストから見た現在のキューサイズ)を捕捉する使用例を挙げました。 では、非同期のケースでは、ユーザーは UpDownSumObserver ではなく ValueObserver を使うべきかどうかをどのように判断すればよいでしょうか。

キューの(cumulativeな)サイズを非同期に報告する方法を考えてみましょう。 このケースでは ValueObserverUpDownSumObserver の両方が論理的に当てはまります。 非同期インストゥルメントは間隔ごとに1つの計測しか捕捉しないため、この例では SumObserver は現在のsumを報告し、ValueObserver は現在のsum(最大値・最小値に等しい)と1に等しいcountを報告します。 集約がない場合、これらの結果は等価です。

推奨されるのは、より適切なデフォルト集約を持つインストゥルメントを選ぶことです。 マシン群にわたるキューサイズを観測していて、集約されたキューサイズだけが知りたいのであれば、SumObserver を使用してください。 マシン群にわたるキューサイズを観測していて、それらのマシン間でのキューサイズの分布に関心があるのであれば、ValueObserver を使用してください。

検討された他の名前: GaugeObserverLastValueObserverDistributionObserver

詳細Q&A

なぜ ValueRecorderValueObserver にMinMaxSumCountを使うのか

この2つの非加算的インストゥルメントに対する MinMaxSumCount の選択について、疑問が呈されてきました。 これらのインストゥルメント種別のデフォルト集約で4つの値を使うということは、これら2つのインストゥルメント種別について4つの値がエクスポートされることを意味します。 Min、Max、Sum、Countという選択は、コストの低いデフォルトとして意図されたものですが、さらに最小限のデフォルト集約も選択できます。 疑問はこうでした。これらのインストゥルメントのデフォルト集約は「SumCount」にすべきか。 「SumCount」を使うと、レートと平均は監視できますが、値の範囲は監視できないことを意味します。

本提案は引き続き、これら2つのインストゥルメントに対してMinMaxSumCountの使用を規定します。 私たちは、パフォーマンスやコストが懸念される場合、通常、より低コストで適用できる加算的インストゥルメントが存在すると考えています。 ValueObserver の場合は、SumObserverUpDownSumObserver の使用を検討してください。 ValueRecorder の場合は、デフォルトよりもコストの低いビューをこれらのインストゥルメントに設定することを検討してください。

ValueObserver の時間的性質はDeltaかInstantaneousか

ValueObserver の計測にDelta対Instantaneousのどちらの時間的性質のラベルを付けるべきかについて、疑問が呈されてきました。 これには関連する疑問もあります。1つの収集間隔につき1つの計測しか生成しないかもしれない非同期インストゥルメントに対して、MinとMaxの値を集約するとはどういうことでしょうか。

デフォルト集約を定義する目的は、間隔ごとに計測が1つしかない場合に、複数の収集間隔にわたって値がどのように集約されるかを規定することにあります。 集約が適用されていない場合、1回の収集間隔に対するMinMaxSumCount集約の結果は、Min、Max、Sumに等しい単一の計測値と、1に等しいCountになります。 ValueObserver の計測に集約を適用する前は、それをInstantaneous計測として明確に定義できます。 収集間隔の終わり近くの瞬間に捕捉された計測は、前の収集間隔に対してcumulativeでもdeltaでもありません。

OTEP 88 は、この疑問に対応するためにLast Valueとの関係を論じています。 あるインストゥルメントとラベルセットについて ValueObserver の計測を1つ捕捉した後、その計測は次の計測が行われるまで、そのインストゥルメントに関連付けられたLast valueになります。

ValueObserver の計測を空間次元にわたって集約するとは、有効な時点における分布へとlast valueを結合することを意味します。 この場合のMinMaxSumCount集約とは、Min値とMax値、計測のsum、そして計測に寄与した区別されるラベルセットのcountを計算することを意味します。 集約された結果はinstantaneousと見なされます。それは異なるマシンから得られたデータポイントを使い、それぞれ異なる収集間隔を使って計算されたものかもしれないためです。 集約値は、協調していない収集間隔の結果を平均化しているため、時間に関して近似的なものとみなす必要があります。 1分間の収集間隔からのlast valueと、10秒間の収集間隔からのlast valueを組み合わせているかもしれません。その結果は、空間次元にわたるinstantaneousな分布の要約です。

あるインストゥルメントとラベルセットについて ValueObserver の計測を時間次元にわたって集約すると、ある期間にわたって取得された一連の計測が得られますが、だからといってこれらを自動的にdelta計測とみなすことにはなりません。 あるラベルセットについて連続する10個の収集間隔を集約した場合、得られるのはCountが10に等しいinstantaneous計測の分布であり、Min、Max、Sumは平均値と、その分布に存在する値の範囲を伝える役割を果たします。 その結果は、instantaneous計測の時間平均分布です。

時間軸にわたって集約する場合も空間軸にわたって集約する場合も、ValueObserver インストゥルメントの結果はInstantaneousな時間的性質を持つと論じられてきました。

ValueObserver 計測の時間的・空間的集約

ValueObserver の計測を空間次元と時間次元の両方にわたって集約する際は、より短い収集間隔で計算された結果に偏りが生じないよう、慎重に行う必要があります。 空間次元にわたる時間平均集約では、次のように収集間隔を考慮する必要があります。

  1. 問い合わせ対象の期間を決めます。たとえば [T_begin, T_end] とします。
  2. その期間を一連のタイムスタンプに分割します。たとえば [T_begin, T_begin+(T_end-T_begin)/2, T_end] とします。
  3. 区別されるラベルセットとタイムスタンプごとに、そのタイムスタンプにおけるlast valueの定義を使って空間集約を計算します。これにより、比較可能なcountを持つ、タイムスタンプ付きの集約計測の集合が得られます。
  4. ステップ3で得られたタイムスタンプ付き計測を集約します。

ステップ2とステップ3は、先に空間集約を計算することによって、より頻度の低い計測が出力の中で等しく表現されることを保証します。 もし先に時間集約を計算してから空間次元にわたって集約すると、より高い頻度で収集されたインストゥルメントの方が、それに応じて多くのポイントを集約結果に寄与させることになります。 したがって、時間にわたって平均化する前に、ValueObserver インストゥルメントを空間次元にわたって集約しなければなりません。

未解決の問題

タイミングインストゥルメント

一部のメトリクスAPIに見られる、潜在的に重要な特殊目的インストゥルメントの1つに、タイミングを報告するための専用インストゥルメントがあります。 その根拠は、タイミングを報告する際には単位を正しく扱うことが重要であり、しばしば容易ではないというものです。 多くのプログラミング言語は、時刻や時刻の差を表すのに異なる型を使用します。 タイミングの分布を正しく報告するには、OpenTelemetryは ValueRecorder を使用しつつ、使用されたクロックが出力する単位に合わせて設定する必要があります。

過去には、専用の TimingValueRecorder インストゥルメントを作るという提案が却下されました。 このインストゥルメントは ValueRecorder と同一ですが、Record() メソッドは、単位が正しく自動的に設定されるよう、期間を表すのに使われる正しい型に特化したものになります。 これに関連するパターンとして、計測とタイミングの捕捉の両方を担う TimerStopWatch インストゥルメントがあります。

このような型はヘルパーとして追加すべきでしょうか。 たとえば、TimingValueRecorder は実際のインストゥルメントであるべきでしょうか、それとも ValueRecorder をラップするヘルパーであるべきでしょうか。 TimingValueRecorder をヘルパーにすると、その存在感や標準としての位置づけが弱まり、まったく用意しないことが計装のミスを助長するのではないかという懸念があります。

これは将来再検討されるかもしれません。

同期のcumulativeと非同期のdeltaのためのヘルパー

Cumulative計測は、最後に報告された値を記憶しておくことでdelta計測に変換できます。 ヘルパーインストゥルメントは、最後に報告された値を記憶し、同期的にdeltaを報告することで、同期のcumulative計測をエミュレートすることを提供できるかもしれません。

Delta計測は、報告されたすべての値のsumを記憶しておくことでcumulative計測に変換できます。 ヘルパーインストゥルメントは、この方法で非同期のdelta計測をエミュレートすることを提供できるかもしれません。

需要があれば、この種のヘルパーを標準化すべきでしょうか。 これらのヘルパーは多くの注意点を伴うため標準からは除外されていますが、ヘルパーとしてであれば、一般にOpenTelemetry SDKができないことを容易に行うことができます。 たとえば、私たちが同期のcumulativeインストゥルメントを避けているのは、SDKがサポートすることを要求されていない順序性を暗黙のうちに要求するように見えるためですが、ロックを使用するインストゥルメントヘルパーであれば容易にdeltaへ変換できます。

このようなヘルパーは標準化すべきでしょうか。答えはおそらくノーです。