Datadogでもサポート(ベータ版)されているOpenTelemetryのSpan Linkを試した

はじめに

エス・エム・エスでエンジニアをしている @koma_koma_d です。みなさん、オブザーバビリティ、やってますか?今回の記事では、OpenTelemetryのSpan Linkについて紹介します。

そもそも、トレース、スパンとは?

オブザーバビリティにおける主要なシグナルとして、「ログ」「トレース」「メトリクス」の3つが挙げられます。その一つであるトレースは、システムの処理の流れをエンドツーエンドで追跡するための仕組みです。1つのリクエストが処理される流れを、部分に分割して可視化することができます。

トレースは、以下のような「スパン」のツリー構造として表現されます。スパンは、処理中の特定の操作を表現します。データベースへのクエリ、外部サービスのAPIコール、あるいはアプリケーション中の特定の処理ブロックなど、様々なものをこのスパンとして表現することができます。

この構造は、あるスパンを開始するときに、そのスパンが子としてぶら下がることになる「親」のスパンを指定することで作られます(「親」のないスパンが、1つのトレースのルートスパンとなります)。たとえば、Webアプリケーションのサーバ側のコントローラーがリクエストを受け付けたところでルートスパンを開始し、何らかの業務上の処理のまとまりをその子スパンにし、その中で行われるデータベースへのアクセスを更に子スパン(ルートスパンから見た孫スパン)にする、といった形です。

1つのトレースを構成するスパンは、システム内の単一コンポーネントから発行される場合もあれば、システム内の複数のコンポーネントから発行される場合もあります(後者の場合のトレースを特に「分散トレース」と呼びます)。トレースを活用することで、一続きの処理の中での個々の処理同士の依存関係を可視化することができ、たとえばどこの処理の遅延が全体の処理時間にインパクトを与えているか、どの部分でのエラーが起きたか、などを簡単に見ることができるようになります。

なお、分散トレースを実現するためには、サーバ間の通信時にスパンの情報を伝播させ、受信側で新しくスパンを開始する際にそれを親スパンとして指定する必要があります。この際に用いられるのがSpanContextで、これにはトレースID・スパンIDなどのスパンを特定する情報と、追加的な情報が含まれます。このSpanContextはシリアライズして送信されるのですが、後述するSpan Linkの場合にもこれを用いることになります。

opentelemetry.io

トレースは親子関係を持つスパンの集まりでした。しかし、ある一連の処理の流れについて、全てのスパンを親子関係で繋ぐことが常に最適であるとは限りません。親子関係とするには適さないが、関連があることは表現したいという場合に用いるのが「Span Link」です。

Span Linkについては、OpenTelemetryのドキュメントにも説明があります。

Span Linkによる関係が親子関係と異なるのは、

  • 同じトレースに属さなくてもよい(親子関係の場合、子は親の属するトレースに属することになる)
  • 1つのスパンから、複数のスパンに対して関係を持つことができる(親子関係の場合、親は1つしか持てない)

の2点です。すなわち、これらの特徴が活きるシーンが、親子関係ではなくSpan Linkを用いる場面ということになります。上記のドキュメントのうち、仕様の概要を記した資料に、Span Linkが使いやすい場面が紹介されています。

1つ目は、前段の処理とは別のトレースとして新たに開始したい場面です。親子関係ではなくSpan Linkを使うことで、別のトレースでありながら前段のトレース(のうちのリンク先のスパン)との関連があることを示すことができます。

2つ目は、scatter/gatherと言われている、処理が一度複数に分岐して実行され、後から合流して続きの処理が行われるようなパターンです。スパンには親スパンは1つしか設定できないので、合流する際に直前の(別々に実行された)複数の処理のスパンを親とすることはできませんが、Span Linkであればそれら全てに対して関係を持たせることができます。

Span Linkを設定する方法:C#の場合

Span Linkを設定するのは簡単です。C#の場合は、System.Diagnostics名前空間のクラス群を使って実現できます。たとえば、SQS経由で起動される非同期処理のコンシューマ側のスパンから、プロデューサ側のスパンへリンクを作る場合には以下のようになります。なお、SpanContextの情報をSQSのメッセージに載せたり抽出したりする部分は、親子関係を作る場合でも共通です(自動計装の場合は、コードを書かなくてもライブラリ側でやってくれているかもしれません)。

/* プロデューサ側 */
// SpanContext情報を載せる
Propagators.DefaultTextMapPropagator.Inject(
    new PropagationContext(activity.Context, Baggage.Current),
    message.MessageAttributes,
    (attributes, key, value) => attributes[key] = new MessageAttributeValue {DataType = "String", StringValue = value});
/* コンシューマ側 */
// SpanContext情報を抽出
var context = Propagators.DefaultTextMapPropagator.Extract(
    default, message.MessageAttributes,
    (attributes, key) => attributes.TryGetValue(key, out var value) ? [value.StringValue] : []);
// Span Linkを指定してアクティビティを開始
using var activity = activitySource.StartActivity(
    "ConsumeMessage", ActivityKind.Consumer,
    default(string?), tags, [new ActivityLink(context.ActivityContext)]);

これでOpenTelemetry上のSpan Linkを作ることができます。

※ちなみに、 DefaultTextMapPropagator を使った場合、トレースID・スパンIDなどの情報は traceparent という名前の属性として登録されます(TraceStateの情報も載せた場合は tracestate に載ります)。これは traceparent という名前のヘッダーをつけるのがW3CのTrace Contextの仕様だからです。

www.w3.org

Span Linkはどのように可視化できるか?:Datadogの場合

さて、以上のようにして作ったSpan Linkは、オブザーバビリティツールの上ではどのように可視化されるのでしょうか。ここでは、Datadogの例を紹介します。Datadogでは、エージェントのv7.45.0でSpan Linkがサポートされるようになり、Web UI上は現時点でベータ版でSpan Linkがサポートされています。

先述の例のように、キューのコンシューマ側のスパンからプロデューサ側のスパンに対してリンクを作った場合、Datadogのコンシューマ側のトレースの画面で、中段の一番右に「Span Links BETA」のタブが現れます。そのタブを選択すると以下のように表示されます。

画面は説明がほとんど不要なほどにシンプルで、下段にSpan Linkの一覧(今回の例では1つだけ設定しているので1つだけ)が表示されます。どのスパンからどのスパンにリンクされているかが表示され、リンク先のスパンの画面にクリック一発で遷移することができます。便利ですね!

おわりに

以上、OpenTelemetryのSpan Linkについて、実際のコード例も交えて紹介しました。何かの参考になれば幸いですが、私自身まだまだOpenTelemetryの仕様を把握しきれておらず、有効活用できていないところがあります。

特に、今回ドキュメントに即してSpan Linkを使う場面の1つとして紹介した「1トレースとするのが適当ではない場面」については、正直ピンときていないところがあります。基本的には1トレースとして扱った方がオブザーバビリティツール上で一覧しやすいなどの点でメリットがあるように思うので、別トレースとして扱う方が好ましい場面というのがあまりピンときていません。ドキュメントでは、「サービスの信頼できる境界の内側に入って、サービスのポリシーが新しくトレースを始めることを要求する場合」と書かれているのですが、あまり具体的にイメージできていません。また、このようなポリシー上の事情以外にも、別トレースとして扱う方が好ましい場合があるのだろうか、というのもよくわかっていません。もしこのあたり知見や「うちはこうしてるよ」というのがある方がいたらぜひ教えていただきたいです。

私の運用しているアプリケーションでは、OpenTelemetryを使うことで、かなりエラー発生時などの調査がやりやすくなったと感じています。とはいえ、まだまだOpenTelemetryをフル活用できているとは言い難い状態ですので、これからも勉強しながら活用していきたいと思います。

もし記事中に誤った記述や技術活用上の改善点などがありましたら、筆者のXアカウント @koma_koma_d までぜひお気軽にご連絡ください!