Summary
The Elasticsearch/OpenSearch filter lowering serves 4 of the built-in fields RFC 0005
defines — span.name, span.duration, resource.service, event.name — and refuses the
rest. For most of the refused ones the value is already indexed, so the gap is a missing
mapping from the field to the column that holds it, not missing data.
Verified against a production OpenSearch cluster over a 6-hour window. Counts are trace
counts for an exists query on the attribute spelling of the same value.
| field |
where the value is indexed |
today |
span.traceID |
traceID, top-level keyword |
refused: does not support the built-in field |
span.spanID |
spanID, top-level keyword |
refused: does not support the built-in field |
scope.name |
otel.scope.name tag — present on 100 traces |
refused: does not index the "scope" level |
scope.version |
otel.scope.version tag — present on 100 traces |
refused: does not index the "scope" level |
link.traceID |
references.traceID, nested keyword |
refused: does not index the "link" level |
link.spanID |
references.spanID, nested keyword |
refused: does not index the "link" level |
span.startTime |
startTime long and startTimeMillis date |
refused: a timestamp constant declares a type this schema cannot search |
event.time |
logs.timestamp, nested |
refused: a timestamp constant declares a type this schema cannot search |
span.kind and span.status are the same shape and are filed separately as #9473.
Two distinct causes
No case for the field. buildComparison and buildMembership
(core/filter.go)
switch on a handful of fields and send everything else to errUnsupportedField, and the
scope and link levels are refused wholesale before any field is looked at. traceID and
spanID are top-level keywords in the span mapping, and references.traceID /
references.spanID are indexed inside the existing references nested type, which the
lowering already knows how to query for other purposes.
The timestamp constant type is unsupported. span.startTime and event.time are
refused for the constant rather than for the field. This one is the most surprising of the
set, because startTimeMillis is mapped as a date specifically so that time-range queries
work — the reader already uses it for the query's own time bounds — and logs.timestamp is
mapped for events. So an ordered comparison on an instant has exactly the right index
waiting for it.
The level-wide refusals are inaccurate as stated
errUnsupportedLevel says the schema "does not index the scope level". It does index part
of it: getTagsFromInstrumentationLibrary
(to_dbmodel.go)
writes otel.scope.name and otel.scope.version as span tags, and from_dbmodel.go reads
them back into the scope and removes them from the span. What the schema genuinely drops is
the scope's attributes, which that function never writes.
The link level is the same: dbmodel.Reference keeps only the trace ID, span ID and
reference type, so link attributes are genuinely absent, while the two IDs are indexed.
So scope.attr(...) and link.attr(...) are correctly refused and should stay refused; it
is the four built-in fields beside them that have somewhere to go.
Suggested fix
Refuse per field rather than per level, and add cases for the fields whose column exists:
span.traceID, span.spanID, scope.name, scope.version, link.traceID and
link.spanID. Separately, support the timestamp constant type so span.startTime and
event.time can lower to a range over startTimeMillis and logs.timestamp.
Reword errUnsupportedLevel so it says which part of a level is unindexed, since a level
that stores its identity but not its attributes is the common case rather than the exception.
Environment
Jaeger f7d52c13a, OpenSearch, both api_v3 bindings, filters behind the
jaeger.query.structuredFilters feature gate.
Summary
The Elasticsearch/OpenSearch filter lowering serves 4 of the built-in fields RFC 0005
defines —
span.name,span.duration,resource.service,event.name— and refuses therest. For most of the refused ones the value is already indexed, so the gap is a missing
mapping from the field to the column that holds it, not missing data.
Verified against a production OpenSearch cluster over a 6-hour window. Counts are trace
counts for an
existsquery on the attribute spelling of the same value.span.traceIDtraceID, top-level keywordspan.spanIDspanID, top-level keywordscope.nameotel.scope.nametag — present on 100 tracesscope.versionotel.scope.versiontag — present on 100 traceslink.traceIDreferences.traceID, nested keywordlink.spanIDreferences.spanID, nested keywordspan.startTimestartTimelong andstartTimeMillisdateevent.timelogs.timestamp, nestedspan.kindandspan.statusare the same shape and are filed separately as #9473.Two distinct causes
No case for the field.
buildComparisonandbuildMembership(
core/filter.go)switch on a handful of fields and send everything else to
errUnsupportedField, and thescope and link levels are refused wholesale before any field is looked at.
traceIDandspanIDare top-level keywords in the span mapping, andreferences.traceID/references.spanIDare indexed inside the existingreferencesnested type, which thelowering already knows how to query for other purposes.
The timestamp constant type is unsupported.
span.startTimeandevent.timearerefused for the constant rather than for the field. This one is the most surprising of the
set, because
startTimeMillisis mapped as adatespecifically so that time-range querieswork — the reader already uses it for the query's own time bounds — and
logs.timestampismapped for events. So an ordered comparison on an instant has exactly the right index
waiting for it.
The level-wide refusals are inaccurate as stated
errUnsupportedLevelsays the schema "does not index the scope level". It does index partof it:
getTagsFromInstrumentationLibrary(
to_dbmodel.go)writes
otel.scope.nameandotel.scope.versionas span tags, andfrom_dbmodel.goreadsthem back into the scope and removes them from the span. What the schema genuinely drops is
the scope's attributes, which that function never writes.
The link level is the same:
dbmodel.Referencekeeps only the trace ID, span ID andreference type, so link attributes are genuinely absent, while the two IDs are indexed.
So
scope.attr(...)andlink.attr(...)are correctly refused and should stay refused; itis the four built-in fields beside them that have somewhere to go.
Suggested fix
Refuse per field rather than per level, and add cases for the fields whose column exists:
span.traceID,span.spanID,scope.name,scope.version,link.traceIDandlink.spanID. Separately, support the timestamp constant type sospan.startTimeandevent.timecan lower to arangeoverstartTimeMillisandlogs.timestamp.Reword
errUnsupportedLevelso it says which part of a level is unindexed, since a levelthat stores its identity but not its attributes is the common case rather than the exception.
Environment
Jaeger
f7d52c13a, OpenSearch, bothapi_v3bindings, filters behind thejaeger.query.structuredFiltersfeature gate.