Semantic Entity Model¶
The semantic model turns flat OpenTelemetry attributes into entities and typed relationships.
Registry layers¶
The effective registry is a merge of:
- the pinned OpenTelemetry semantic-conventions model under
tools/semconv_codegen/upstream/otel-semconv/v1.43.0/model; - project extensions under
tools/semconv_codegen/model/extensions.
Extensions may reference upstream attributes and entities, but may not redefine them. The upstream snapshot is build input and is never downloaded at runtime.
Entities¶
An entity definition has:
- a stable entity name such as
service,k8s.pod, orapp.endpoint; - one or more attribute references;
- at least one identifying attribute if a runtime class should be generated;
- optional descriptive metadata such as stability and a brief.
- id: entity.app.endpoint
type: entity
name: app.endpoint
stability: development
attributes:
- ref: service.name
role: identifying
- ref: service.namespace
role: identifying
- ref: http.request.method
role: identifying
- ref: http.route
role: identifying
Java SemanticRegistry constructs a deterministic ID from identifying values:
The generated Python class is immutable, receives that stored ID as
element_id, and validates exact registry field types without rebuilding the
identity. Missing any identifying key means that Java does not observe the entity. Present but
empty, incorrectly typed, or invalid enum values are rejected. Optional
attributes enrich an entity but do not change its identity. Array and template
attributes retain their registry types; template fields such as
k8s.pod.label.<key> are exposed as canonical dotted attributes.
Identity fields are limited to strings, integers, booleans, and enums without
floating-point members. Floating-point values remain valid descriptive
attributes but cannot participate in an entity ID.
ETL hierarchy¶
The local ETL extension models observed execution topology with hierarchical identity:
| Entity | Identifying fields | Descriptive fields |
|---|---|---|
etl.pipeline |
etl.pipeline.id |
etl.pipeline.name |
etl.run |
etl.pipeline.id, etl.run.id |
etl.run.name |
etl.part.run |
etl.pipeline.id, etl.run.id, etl.part.run.id |
etl.part.name |
The registry defines Pipeline -> contains -> Run -> contains -> Part Run.
Retries preserve the same logical Part Run identity. A later Pipeline Run uses
a new Run ID and therefore creates a new Run and Part Run.
Extraction is additive: Pipeline identity alone creates only a Pipeline; adding Run identity creates the Run and first relationship; complete Part Run identity creates the full hierarchy. ETL executions without a matched interaction remain undiscovered.
Relationships¶
A relationship names a directed edge between entity types:
- id: relationship.service_exposes_app_endpoint
type: relationship
name: exposes
source_entity: service
target_entity: app.endpoint
source_signals: [service_graph, span_metrics]
evidence_scope: observation
An observation relationship is expanded when both endpoint entities are
complete in one client, server, or root observation. An interaction
relationship connects entities from the client observation to entities from the
server observation and declares the Collector selector that chooses it:
- id: relationship.service_calls_service
type: relationship
name: calls
source_entity: service
target_entity: service
source_signals: [service_graph]
evidence_scope: interaction
connection_type: unset
Missing, empty, and Collector-provided unset selectors normalize to unset.
Unknown selectors are rejected without discarding valid client or server
observation topology. Only a relationship explicitly allowed by the registry is
emitted, and every expansion suppresses edges whose endpoint IDs are equal.
source_signals declares actual evidence eligibility. Servicegraph datapoints
can expand definitions containing service_graph; root discovery datapoints can
expand definitions containing span_metrics. For example, executes declares
only span_metrics, so servicegraph observations cannot create transactions.
Code generation creates a frozen concrete Pydantic edge class for every
relationship definition. For example, relationship.service_calls_service
becomes ServiceCallsServiceEdge. Each class declares its relationship and
endpoint semantic types, validates endpoint IDs, and computes the same
deterministic edge ID used by Flink. Structural edge attributes are
preserved without embedding endpoint entities.
From attributes to the live graph¶
The generation pipeline connects the model to runtime behavior:
python -m tools.semconv_codegenvalidates and merges both registry layers.- It builds a strict semantic intermediate representation and commits
semantic-entities.schema.json. - Pinned
datamodel-code-generatorturns that JSON Schema into static Pydantic field classes, while the project generator adds semantic IDs, registries, and concrete edge models. - The generated Python remains static, importable, and IDE-friendly; no model generation or registry parsing occurs at runtime.
- The same operation selects scalar attributes used by service-graph relationships.
- Collector backends include those dimensions in their metrics.
- Flink interprets client and server dimensions through the generated package.
- Interaction events carry normalized graph nodes and edges.
- Generic consumers can display custom types without embedding registry logic.
Template attributes ending in .label, .annotation, or .selector and
non-scalar attributes are excluded from Collector dimensions.
Cardinality¶
Entity identity often requires high-cardinality values such as pod UIDs, routes, or instance IDs. Including them as metric dimensions increases service-graph time-series cardinality.
Treat every identifying attribute as a storage and throughput decision:
- use stable bounded names where possible;
- do not use request IDs, trace IDs, user IDs, or session IDs;
- estimate combinations across client, server, route, namespace, and workload;
- monitor Collector memory and Kafka throughput after adding dimensions.
The generator deliberately follows registry policy. It does not silently remove a dimension because its cardinality appears risky.