はじめに
この記事はDatadog Advent Calendar 2025 2 日目の記事です 🎄
ABEMA の広告配信システム開発チームでバックエンドを担当している戸田朋花です。
Datadog APM にはサービスという概念があり、サービス単位でメトリクスやトレースをみたりサービス間の依存関係をみたりできます。
基本的にはアプリケーションの処理全体をひとつのサービスとして扱います。しかしアプリケーション内でデータベースや外部の API などアプリケーションとは別の処理に依存する場合は、呼び出し元のサービスとは別でメトリクスの把握やモニターの作成等を行いたいため別のサービスとして扱いたいです。
本記事では、このような場合に依存先を自動的に検出する Inferred Services について紹介します。
Datadog APM のサービスについて
Datadog APM におけるサービスとは、エンドポイントやクエリなどをグルーピングする単位です。
Datadog にはサービスの単位で監視するのに便利な機能があります。
例えば Software Catalog ではサービスを一覧して確認でき、Service Map を表示するとサービス間の依存関係をグラフ形式で可視化できます。


また、各サービスの詳細ページではサービス全体のスループットやエラー率などの指標を確認したり、
サービスに紐づいているモニターを一覧して見られたりします。
サービスの検出はトレースについているserviceという予約済みのタグの値を元に行われます。サービス詳細画面で確認できるスループットやエラー率などの指標はserviceタグ単位でトレースメトリクスが可視化されたもので、依存関係もトレースのスパンの親子関係によって検出されます。
このため、あるアプリケーションがデータベースへ依存している時にデータベースへのリクエストをトレースしても何もしない限りデータベースは依存のある別のサービスとして認識されません。
データベースへのリクエストのスループットやエラー率を呼び出し側のサービスとは別で見たかったり、データベースへの依存を Service Map のグラフ上で可視化したい場合に困ります。
これまでこの問題に対してはトレースのserviceタグを子スパンで上書きして実質的に別サービスとして扱う方法が一般的でした。以下のコード例のようにデータベースへリクエストする部分で新たに子スパンを作成してその中でサービス名を上書きすると Datadog 上で呼び出し元と依存関係のある異なるサービスとして検出されます。
span, ctx := tracer.StartSpanFromContext(ctx, "database.selectUser",
tracer.ServiceName("user-database"),
tracer.SpanType(ext.AppTypeDB),
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
)
これに対する代替手段となるのが Inferred Services です。Inferred Services を利用すればサービス名を上書きせずに依存サービスを検出できます。
Inferred Services とは
Inferred Services とは今計装されていて計測対象になっているサービスが依存しているサービスを自動で検出する機能です。これはデータベースやサードパーティの API など直接計装するのが難しいものも検出できます。
最新の Datadog Agent や OpenTelemetry Collector の Datadog Exporter ではデフォルトで有効になっている機能なので、みなさんの Software Catalog でも Inferred Services というカテゴリが確認できるかもしれません。
Inferred Services では上述のようにサービス名を上書きしなくても他のサービスとして認識できます。
クライアントスパン(span.kind:clientが設定されているスパン)に特定の attribute をつけていると Datadog Agent 等がその attribute をpeer.* 形式の attribute にマッピングします。Datadog はこれらの attribute を検出するとサービス名が同じだったとしても異なるサービスとして扱います。
例えば、クライアントスパンにdb.name:user-dbという attribute が付いているとします。Inferred Service が有効になっているとこの attribute をもとにpeer.db.name:user-dbが作成されます。
Datadog はpeer.db.name:user-dbを検出すると user-db というサービス名の Inferred Service を作成して呼び出し元に依存しているサービスとして扱います。
どの attribute がpeer.* 形式の attribute(Peer Tag)にマッピングされてサービス名に使われるかについてはこちらで確認できます。
Datadog が提供する Go 向けトレーシングライブラリ dd-trace-go には、Redis・データベース・HTTP クライアントなどよく使われる外部ライブラリ向けに計装済みのパッケージが提供されています。実際にどのような attribute がつけられているのか確認してみます。
例えば redis のクライアントライブラリである rueidis のラッパーを確認してみると以下の attribute が設定されていました。
opts := []tracer.StartSpanOption{
tracer.ServiceName(c.cfg.serviceName),
tracer.ResourceName(cmd.statement),
tracer.SpanType(ext.SpanTypeRedis),
tracer.Tag(ext.TargetHost, c.host),
tracer.Tag(ext.TargetPort, c.port),
tracer.Tag(ext.Component, instrumentation.PackageRedisRueidis),
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
tracer.Tag(ext.DBSystem, ext.DBSystemRedis),
tracer.Tag(ext.TargetDB, c.dbIndex),
}
tracer.ServiceName(c.cfg.serviceName) ではサービス名を上書きできますが、こちらから特に指定しなければ今設定されているサービス名が利用されます。
これらのタグのキーと値にはext パッケージで定義された定数が利用されています。
中でも tracer.Tag(ext.DBSystem, ext.DBSystemRedis) は、後に peer.* 形式へマッピングされる attribute のひとつです。
ext.DBSystem の実体は "db.system" であり、Datadog Agent 等によって peer.db.system にマッピングされるとこちらに記載されています。これによって Datadog の画面上では、この peer.db.system に設定される値である ext.DBSystemRedis(="redis")をサービス名とした Inferred Service が作成されて表示されます。
Inferred Services がデフォルトで有効になったことに伴い、サービス名を上書きするのは非推奨になりました。サービス名を上書きして依存を検出させる方法は Service Override と呼ばれています。
Inferred Services と Service Override の両方が有効になっていると、Service Override によって作られたサービスと Inferred Services により検出されたサービスの両方が異なるものとして Software Catalog で表示されます。これらは実体は同じなのでノイズになってしまいます。
これを避けるために Service Override の無効化が推奨されています。
しかし、Service Override でつけられたサービス名を用いてダッシュボードやモニターなどを作成している場合は、突然無効化すると壊れてしまうので慎重に行う必要があります。
具体的な手順はこちらにあります。
実際に ABEMA の広告配信システムのあるサービスで Service Override から Inferred Service への置き換えをしました。
置き換え前は以下のコードのように Valkey へのリクエストが生じる部分で子スパンを作成して Service Override を用いて Valkey であることを表現していました。
var opts []tracer.StartSpanOption
host, port, err := splitHostPort(config.InitAddress)
if err == nil {
opts = append(opts,
tracer.Tag(ext.TargetHost, host),
tracer.Tag(ext.TargetPort, port),
)
}
opts = append(opts,
tracer.ServiceName(valkeyServiceName),
tracer.SpanType(ext.SpanTypeValkey),
tracer.Tag(ext.Component, instrumentation.PackageValkeyIoValkeyGo),
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
tracer.Tag(ext.DBSystem, ext.DBSystemValkey),
)
この記述によってサービスの表示は以下のようになっていました。
一番左が呼び出し元のサービス(上書きする前のサービス)で、真ん中が Service Override によって表現されているサービスです。
一番右の blocked-ip-address と表示されているサービスは Inferred Services によって作成されたサービスです。
この時の設定ではext.TargetHostによって設定された値が Peer Tag にマッピングされ、その値が Inferred Service のサービス名として設定されています。
Peer Tag の値が IP address に一致する場合は、高カーディナリティなタグによるメトリクス汚染を防ぐために、”blocked-ip-address”という文字列に置き換えられます。そのためここではサービス名は”blocked-ip-address” と表示されています。

Service Override によって作成したサービスの詳細ページは以下のようになっていました。

サービス名の横に Service Override によって作られたサービスであることが明示されています。
Service Override の利用を辞めるために以下のように書き換えました。
var opts []tracer.StartSpanOption
host, port, err := splitHostPort(config.InitAddress)
if err == nil {
opts = append(opts,
tracer.Tag(ext.TargetHost, host),
tracer.Tag(ext.TargetPort, port),
)
}
opts = append(opts,
tracer.SpanType(ext.SpanTypeValkey),
tracer.Tag(ext.Component, instrumentation.PackageValkeyIoValkeyGo),
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
tracer.Tag(ext.DBSystem, ext.DBSystemValkey),
tracer.Tag(ext.DBName, dbName),
)
まずサービス名の上書きを止めるためにtracer.ServiceName(valkeyServiceName)の記述を削除しました。
そして、サービス名が”blocked-ip-address”になるのを避けるために Peer Tag に変換されるext.DBNameにも値を設定しました。Peer Tag が複数ある場合は、より優先順位が高いものがサービス名として利用されます。新たに追加したext.DBNameはpeer.db.nameにマッピングされ、元々サービス名として使われていたext.TargetHostはpeer.hostnameにマッピングされます。peer.db.nameの方がより優先度が高いためこちらがサービス名として利用されます。優先度についてもこちらのドキュメントから確認できます。
この設定を反映すると以下のようになります。

Service Override をやめたため Service Map には Inferred Service のみが表示されています。
また、Inferred Service のサービス名も”blocked-ip-address”からext.DBNameに設定した値に変更されていることが確認できました。
Inferred Service の詳細ページは以下のようになっていて、Service Override の表示が消えています。

終わりに
本記事では Datadog APM の Inferred Service をご紹介しました。
これまでサービスの設定について意識していませんでしたが、Inferred Services の仕組みを知ることでトレース上でサービスがどのように扱われているかがより明確になったと感じています。
Inferred Services は便利な機能ですが、子スパンでサービス名を上書きする運用を行っている場合は影響範囲も大きくなるため、既存の設定を確認しながら慎重に取り入れていく必要がありそうです。
最後まで読んでいただきありがとうございました 🎅
