# OpenTelemetry のエンティティイベントで何ができるのか

Site [llms.txt](/ja/llms.txt)

---

メトリクス、ログ、トレースはシステムがどのように _振る舞うか_ を教えてくれます。
しかし、実際に何が _存在する_ かについてはあまり語りません。
どのホスト、インターフェイス、スイッチ、サービス、ボリュームが今そこにあるのか、そして決定的に重要なこととして、その全体像がこの1時間、1日、あるいは四半期でどう変わったのか。
この動的なインベントリは、オープンなオブザーバビリティスタックにおいて盲点であり続けてきました。

OpenTelemetry の **エンティティイベント** は、Entities SIG の成果であり、[エンティティデータモデル](/docs/specs/otel/entities/data-model/)に記述されています。
これがその盲点を埋め始める要素です。
エンティティイベントは _ストリーム_ です。
興味深い問いは **「届いたあとに何をするのか」** です。
この記事では、オープンソースのコンシューマーを実例として、その問いへの一つの答えを紹介します。

> **Note:** エンティティデータモデルとその規約はまだ **開発中** （安定版ではない）であり、進化を続けています。
> 以下の属性名は例示として扱い、最新の仕様と照合してください。
> ここでの内容はエンティティイベントを活用する一般的な方法論であり、どのようなコンシューマーにも当てはまります。

## エンティティイベントの60秒入門 {#a-60-second-primer-on-entity-events}

OpenTelemetry は _エンティティイベント_ を、エンティティセマンティック規約でアノテーションされた **OTLP ログレコード** として運びます。
各イベントはエンティティの型、識別属性、説明属性、およびライフサイクルを示すイベントタイプを持ちます。
コンシューマーは `otel.entity.event.type` の存在だけでレコードを分類できます。

```text
# エンティティイベントのログレコード（例示）
LogRecord
  Timestamp: 2026-05-26T08:00:00Z              # プロデューサー側のタイムスタンプ
  attributes:
    otel.entity.event.type: entity_state       # 観測された状態、または entity_delete
    otel.entity.type:       host
    otel.entity.id:         { host.name: web-server-1 }            # 識別属性（マップ）
    otel.entity.attributes: { os.type: linux, host.arch: amd64 }   # 説明属性（マップ）
```

プロデューサーがこれらを送信します。
ホストエージェント、ネットワークエージェント、OTLP を話すものなら何でも構いません。
コンシューマーの仕事は、このような一連の観測を _問い合わせ可能な_ ものに変えることです。
その形はパイプラインです。

![パイプライン: OpenTelemetry プロデューサーがエンティティイベントを OTLP 経由で永続的なイベントソースログに送信し、プロジェクションがそのログをリプレイしてバイテンポラルなエンティティグラフをライブで構築する。このグラフは、人間やツール向けの GraphQL API と、AI アシスタント向けの MCP サーバーを通じて公開される。](pipeline.svg)

この記事の残りでは、そのパイプラインの4つのステップを説明します。

## ステップ 1 — 状態ではなくストリームを保存する {#step-1--dont-store-state-store-the-stream}

つい「現在のエンティティ」のテーブルを持ち、行をそのまま更新したくなります。
しかし、それはインフラストラクチャを難しくしているまさにその要素を捨てることになります。
それは **時間** です。
`web-server-1` のアドレスを上書きした瞬間、それが変わったという事実と、いつ変わったかという情報を失います。

より良いデフォルトは **イベントソーシング** です。
すべてのエンティティイベントを永続的な順序付きログに追記し、そのログを正式な記録として扱います。
現在のグラフは _プロジェクション_ です。
ログをインメモリのエンティティとリレーションシップのモデルにリプレイすれば、グラフ全体をゼロから再構築できます。

この方法の利点として、現在の状態は1回のリードで取得でき、**履歴が失われることはありません**。

## ステップ 2 — 意図的にバイテンポラルにする {#step-2--be-bi-temporal-on-purpose}

2つのタイムスタンプが重要です。

- **イベント時刻** — 現実で何かが起きた時刻。
  `LogRecord` のタイムスタンプから取得します。
- **記録時刻** — _あなたが_ それを知った時刻。
  取り込み時に自分でスタンプし、プロデューサーからは決して取得しません。

両方を保持することで、本質的に異なる2つの問いに答えられるようになります。

- _現実のビュー:_ 「先週の火曜日、`db-07` はどのように接続されていたか」
- _監査ビュー:_ 「09:00 の時点で、`db-07` について何を **把握していた** か」

2つ目の問いは、インシデントレビューで重要になる問いであり、2つのタイムラインを統合しなかった場合にのみ答えられます。
初日からバイテンポラル設計にしておく方が、後から導入するよりはるかに低コストです。

## ステップ 3 — エンティティに不変の ID を与える {#step-3--give-entities-an-immutable-identity}

OpenTelemetry はエンティティの **ID を不変** として扱い、信頼できる情報源を目指すグラフにとってこれが正しい規律であることがわかります。
同一性は **厳密に** 一致させます。
観測は既知のエンティティ（同じ ID）か、そうでなければ別のエンティティです。

落とし穴は、 _変化する_ 値を ID に含めてしまうことです。
ホストの ID に現在のリース済み IP を含めると、DHCP の更新でまったく新しいエンティティに分岐してしまいます。
エンティティの生存期間中に安定している属性を選び、正当に変化するもの（現在のアドレス、リソース使用量、最終確認状態など）は _説明_ 属性にします。
そうすれば、アドレス変更は _同じ_ エンティティの属性更新になり、真の同一性の変化は正しく _新しい_ エンティティになります。
2つの異なるものがサイレントにマージされることはありません。

単一の値が時間の経過とともに再利用される場合、修正方法はそれを削除することではなく、識別子と組み合わせることです。
OpenTelemetry の [`process` エンティティ](/docs/specs/semconv/registry/entities/process/#process)は良いモデルです。
PID はリサイクルされる可能性があるため、プロセスは `process.pid` **と** `process.creation.time` の組み合わせで識別されます。
これはそのプロセスの生存期間中は安定しており、変化する事実は説明属性のままです。

これは早い段階で正しく設計する価値があります。
識別属性の値が1つ異なる観測を同じエンティティとして扱う「寛容な」一致は、異なるエンティティをサイレントにマージします。
たとえば、ポートのみが異なるホスト上の2つのデータベースが1つに統合されてしまいます。
信頼できる情報源にとって、サイレントな衝突は、ヒューリスティックの喪失よりも深刻な障害です。

## ステップ 4 — クエリ可能にする {#step-4--make-it-queryable}

時間を考慮したグラフは、人間（および機械）がそれに問い合わせできて初めて有用です。
2つの方法で公開すれば、両方の対象をカバーできます。

- 人間、ダッシュボード、ツール向けの **GraphQL API**。
- AI アシスタントがオペレーターにかわってグラフにクエリできる **Model Context Protocol（MCP）サーバー**。

MCP の側面こそ、エンティティイベントが本当に面白くなるところです。
すべての型、フィールド、引数がリッチな説明を持つため、LLM はスキーマを _イントロスペクト_ し、型付きツール（エンティティの検索、隣接ノードの取得、エンティティ履歴、最近の変更、スキーマの説明）を呼び出して、自然言語で質問に答えられます。

```text
$ ask "先週の火曜日、db-07 はどのスイッチに依存していたか — そしてその後何が変わったか"

→ db-07 の依存パス @ 2026-05-26
    core ← leaf-sw-3, spine-sw-1
  Δ 変更: leaf-sw-3 → leaf-sw-9 (2026-05-28 14:12 UTC)
    spine パスは変更なし
```

ダッシュボードを切り替える必要はありません。
アシスタントが、OTLP エンティティイベントだけから構築された、常に更新される時間を考慮したグラフに基づいて推論します。

## リレーションシップが仕様に入った {#relationships-are-now-in-the-spec}

インベントリは物語の半分にすぎません。
**トポロジー** がもう半分です。
「このサービスはあのデータベースに _依存している_」「このプロセスはあのホスト上で _実行されている_」といった関係です。
この記事が最初に起草されたとき、リレーションシップはまだ将来の作業でした。
それ以降、エンティティイベント仕様は **v1.58.0 仕様リリース** （2026-06-22）で **出荷され**、リレーションシップを直接モデル化しています。
[エンティティイベント](/docs/specs/otel/entities/entity-events/)（[opentelemetry-specification#4836](https://github.com/open-telemetry/opentelemetry-specification/pull/4836)）を参照してください。

リレーションシップはエンティティのステートイベント内に `entity.relationships` 配列として **埋め込まれています**。
各記述子はリレーションシップの `type` と対象エンティティ（その `entity.type` と `entity.id`）を指定します。
方向は `source --[type]--> target` であり、型はオープンな列挙（`depends_on`、`contains` など）です。

```text
# リレーションシップはエンティティステートイベント内に埋め込まれる（仕様 #4836、v1.58.0 で出荷）
LogRecord
  attributes:
    otel.entity.event.type: entity_state
    otel.entity.type:       service.instance
    otel.entity.id:         { service.instance.id: checkout-1 }
    entity.relationships:
      - type:        depends_on
        entity.type: service.instance
        entity.id:   { service.instance.id: payments-1 }
```

エッジはそれを所有するエンティティとともに移動し、個別のイベントとしては存在しません。
リレーションシップの削除は、ソースがその記述子を含まない状態を再送信するだけです。
テンポラルグラフを構築するコンシューマーは、各ステートイベントを読み取り、エンティティをアップサートし、送出エッジを調整します。
配列が時間とともに変化するにつれてリレーションシップが追加・削除され、属性の更新と同じ変更分類に収まります。

## なぜグラフなのか：他のシグナルと結合できるから {#why-a-graph-it-joins-your-other-signals}

ここまでのポイントは、独立したインベントリではなく、すでに持っているテレメトリーを活用することです。
OpenTelemetry はエンティティを [**リソース**](/docs/specs/otel/resource/data-model/) に載せているため、追跡しているものと同じエンティティがメトリクス、ログ、トレースにすでに付与されています。
インベントリとトポロジーグラフは、それらを横断する **結合キー** になります。

- **スクレイプではなくスコープ。**
  グラフを使って _どの_ シグナルを取得するかを決定します。
  やみくもにクエリするのではなく、実際に関心のあるエンティティとトポロジーの一部を選びます。
- **エンティティによる相関。**
  メトリクスのスパイク、ログ行、トレースを、ラベルを手動で照合するのではなく、エンティティの同一性を共有しているという理由で同じホスト、プロセス、サービスに結び付けます。
- **エッジをたどる。**
  リレーションシップ（`depends_on`、`runs_on`）によって、相関は影響範囲の推論に変わります。
  `db-07` の性能が低下したとき、グラフは最初にトレースとメトリクスを調べるべき上流サービスを示します。

何が存在し、どのように接続されているかを示すライブのテンポラルグラフがあれば、残りのオブザーバビリティデータに対してそのような問いを投げかけ、任意の時点について答えることができます。

## プロデューサー側を汎用に保つ {#keep-the-producer-side-generic}

コンシューマーは **あらゆる** OpenTelemetry プロデューサーから取り込み、プロプライエタリなプロトコルではなく標準を話すべきです。
独自の Collector を実行したり、デバイスを直接ポーリングしたりしません。
ホスト、ネットワーク機器、クラウド API からエンティティイベントを送信するのはプロデューサーの仕事です。
プロデューサー側を汎用に保つことが、エコシステムをオープンに保つのです。

## いくつかの運用上の注意点 {#a-few-operational-notes}

信頼できる情報源を目指すコンシューマーは、いくつかの現実に向き合う必要があります。

- **クロックスキュー。**
  イベント時刻はプロデューサーから来ますが、プロデューサー間の時計はずれます。
  プロデューサーをまたぐ単一のグローバルな順序を想定しないでください。
  エンティティごとのタイムラインで推論し、「何を把握していて、いつ把握したか」を判断する際に決着を付ける基準として、自身の記録時刻を保持します。
- **ボリュームとハートビート。**
  プロデューサーは定期的にエンティティを再アサートするため、大半のイベントは「変更なし」を意味します。
  連続する変更なしの観測を統合して（一連の最初と最後を保持して）、定常状態のトラフィックでログが膨張しないようにします。
  ただし、リレーションシップの出現や消失のような構造的変更はそのまま保持します。
- **サイレントマージ。**
  厳密な同一性の裏返しとして、2つの異なるエンティティが偶然に同じ識別キーを共有すると、1つに統合されてしまいます。
  識別キーはプロデューサーとの動作保証として扱い、静かなマージよりも、拒否やフラグ付きの観測として大きく失敗することを選びます。

## まとめ {#takeaways}

- エンティティイベントは「何が存在し、どのように接続されているか」をファーストクラスの OpenTelemetry データに変えます。
- ミュータブルなテーブルではなく、**イベントソース型のバイテンポラル** ストリームとして活用します。
- エンティティの **ID は不変** として扱い、厳密に一致させます。
  変動する事実は説明属性に入れ、履歴が変更に耐えられるようにします。
- グラフを人間（GraphQL）とアシスタント（MCP）の両方に公開します。
  自然言語クエリのストーリーは、関心を持つべき強い理由です。
- リレーションシップは仕様化されています。
  各エンティティのステートイベントに埋め込まれ、v1.58.0 仕様リリースで出荷されています。
- エンティティはリソースに載るため、グラフは既存のメトリクス、ログ、トレースの結合キーとなります。

## 参加するには {#get-involved}

- [エンティティデータモデル](/docs/specs/otel/entities/data-model/)と [OTEP 0256](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.58.0/oteps/entities/0256-entities-data-model.md) を読み、OpenTelemetry の **Entities SIG** および **Semantic Conventions** での議論に参加してください。
  エンティティイベントとリレーションシップの作業は v1.58.0 で出荷され、現在も進行中です（アイデンティティスコープなどが開発中）。

_この記事の基礎となった仕様の策定に対して、Entities SIG に感謝します。_
