Skip to content

fix(warehouse): read renamed OTel attributes under either semconv spelling - #560

Merged
Makisuo merged 3 commits into
mainfrom
fix/deployment-environment-name-dual-key
Aug 21, 2026
Merged

fix(warehouse): read renamed OTel attributes under either semconv spelling#560
Makisuo merged 3 commits into
mainfrom
fix/deployment-environment-name-dual-key

Conversation

@Makisuo

@Makisuo Makisuo commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

What

Two OpenTelemetry attribute renames were being read on the deprecated spelling alone, inside materialized views — where the loss is invisible and permanent for the rows already written.

Rename Read before this PR Consequence
deployment.environmentdeployment.environment.name deprecated key only, in all 14 MVs that pre-extract DeploymentEnv (infra queries read only .name — the mirror image) a service on a current OTel SDK materialized an empty environment into every rollup: no env facet, no env filter, on exactly the dashboards the rollups back
messaging.destinationmessaging.destination.name deprecated key only, in service_external_edges_hourly_mv + its raw branch TargetName fell through to the messaging system, so every topic/queue collapsed into one kafka/sqs node instead of per-destination edges

Both were self-demonstrating: our own SDKs and ingest gateway dual-emit the environment (which is why it never showed on our telemetry), and our own VcsSyncQueue producer spans emit messaging.destination.name (which is why that one was visible on our own service map).

How

One module — packages/domain/src/tinybird/semconv-renames.ts — holds the coalescing fragment for every renamed key Maple actually keys on, in the shape db-query-shape-sql.ts already established:

coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''), ResourceAttributes['deployment.environment'])
coalesce(nullIf(SpanAttributes['messaging.destination.name'], ''), SpanAttributes['messaging.destination'])
  • Write side — every affected MV body; ClickHouse migration 0020 (DROP VIEW + CREATE MATERIALIZED VIEW for the 15 affected views, requiredForIngest: false); local-store mirror: schema v10 snapshot, history identity, v9 → v10 edge.
  • Read side — query-helpers, logs, errors, metrics, service-map(-rollup), infra (including facets). Generic attribute filters coalesce through a new RESOURCE_SEMCONV_ALIASES table in traces-shared.ts, so a filter typed as either spelling matches either stored key.
  • Tests — gates that fail if any MV reads a deprecated key on its own, byte-identity checks between each SQL constant and its DSL expression, and both-spellings filter coverage.

Audit

I checked all 30 pairs in the repo's own ATTRIBUTE_RENAMES dictionary against the read paths, plus resource-level renames that dictionary deliberately excludes. Already correct: HTTP method/status, the whole db.* family, http.targeturl.path, http.host/net.peer.nameserver.address, enduser.iduser.id. Absent entirely: telemetry.auto.version, faas.id, container.labels, otel.library.*. Not audited: metric names/units, log-record semconv, gen-ai attributes.

Known and not fixed here: the query-builder and autocomplete dictionaries still advertise http.method / http.status_code / deployment.environment as suggested keys. Filters coalesce so they work, but we're teaching users deprecated spellings.

⚠ Breaking

The client SDK emitted the deprecated browser.user_agent; it now emits user_agent.original. Nothing in our read path reads the old key (session replay reads a UserAgent column; inspect-trace already lists the semconv key), but a customer query or dashboard keyed on browser.user_agent will stop matching on the next SDK release.

Reviewer notes

  • Forward-only. Rows already materialized keep what the old bodies wrote and converge as the target TTL rolls. Backfilling would mean rewriting every rollup from traces for values that were empty or mislabeled on those rows anyway.
  • The legacy environment emit stays — no longer for our rollups, but for pre-0020 rows and BYO-ClickHouse orgs on a pre-0020 schema. Docs and the telemetry-conventions skill previously said "keep until the MVs coalesce"; they now describe the actual remaining constraints.
  • Byte-identity is load-bearing: the service-map and service-overview routes union an MV branch with a raw-traces branch, and they only merge if both sides compute the same grouping key (asserted by test).
  • SQL baseline diff is the coalesced expressions plus the fingerprints of the queries containing them — nothing else moved.

Deploy

Needs the manual prod ClickHouse migration and bun run --cwd apps/api tinybird:deploy. Both renames ride one migration and one deploy.

Verification

  • bun typecheck — 40/40
  • @maple/domain 555 ✓ · @maple/query-engine 1227 ✓ · @maple/effect-sdk client 32 ✓ · CLI migration tests ✓ · targeted apps/api + apps/web
  • clickhouse:schema:check, tinybird:manifest:check, local schema manifest gate — clean

…v key

OpenTelemetry renamed the resource attribute: the registry lists
`deployment.environment.name` as stable and plain `deployment.environment`
as deprecated ("Replaced by `deployment.environment.name`"). We dual-emit
both on the write side, but every read was pinned to exactly one spelling —
traces, logs, errors, metrics and the service map read the legacy key, infra
read `.name`, and all 14 MV bodies pre-extracted `DeploymentEnv` from the
legacy key alone.

So a service instrumented with an OTel SDK new enough to have adopted the
rename materialized an EMPTY environment into every rollup: no environment
facet, no environment filter, on exactly the dashboards the rollups back.
Our own SDKs dual-emit, which is why this never showed on our telemetry.

One canonical expression now backs both sides (`DEPLOYMENT_ENV_SQL` /
`deploymentEnvExpr`, mirroring the existing `db.system.name` fragment):

  coalesce(nullIf(ResourceAttributes['deployment.environment.name'], ''),
           ResourceAttributes['deployment.environment'])

- write: every MV body, ClickHouse migration 0020 (DROP+CREATE for the 15
  affected views, requiredForIngest false), and the local-store mirror —
  schema v10 snapshot, history identity, v9 -> v10 edge.
- read: query-helpers, logs, errors, metrics, service-map(-rollup) and infra
  (including its facets) go through the shared expression. Generic attribute
  filters coalesce via a new RESOURCE_SEMCONV_ALIASES table, so a filter
  typed as either spelling matches either stored key.
- tests: a gate that fails if any MV extracts DeploymentEnv another way, a
  byte-identity check between the SQL constant and the DSL expression, and
  both-spellings filter coverage. SQL baseline diff is env lines plus
  fingerprints only.

Forward-only: rows already materialized keep the empty DeploymentEnv the old
bodies wrote and age out with the target TTL. Backfilling would mean
rewriting every rollup from `traces`.

The legacy emit stays — not for our rollups any more, but for pre-0020 rows
and for BYO-ClickHouse orgs still on a pre-0020 schema. Docs and the
telemetry-conventions skill now say that instead of "keep until the MVs
coalesce".

Deploy needs the manual prod ClickHouse migration and a Tinybird deploy.
The probe asserts the promoted store's marker equals the CURRENT head
identity, so it fails by construction on a schema bump — which is what it is
for. Move it from v9/2516215f22b41a63 to v10/c10dd07518c57e5d.
…ey too

Audited every rename in the repo's own dictionary (`ATTRIBUTE_RENAMES`,
30 pairs) against the read paths. The HTTP method/status aliases, the whole
`db.*` family, `http.target`/`url.path`, `http.host`/`net.peer.name` →
`server.address` and `enduser.id` → `user.id` all already coalesce. One did
not: `messaging.destination` → `messaging.destination.name`.

`service_external_edges_hourly_mv` and its raw-`traces` branch key `TargetName`
off the deprecated spelling alone, so a producer span from current
instrumentation fell through to the messaging *system* — every topic and queue
collapsing into a single `kafka` / `sqs` node instead of a per-destination
edge. Our own `VcsSyncQueue` spans emit `messaging.destination.name`, so this
was visible on Maple's own service map.

Same treatment as the environment key, and folded into the same migration
rather than a second one: MV body, raw branch, migration 0020 (now
`0020_semconv_key_renames`), local schema v10, one Tinybird deploy for both.

`deployment-env-sql.ts` becomes `semconv-renames.ts` — one module for the
coalescing fragments of keys we key on, which is the shape this should have
had from the start now that there are two. Renames whose keys are only ever
surfaced stay in the recommendation dictionary.

Also: the client SDK emitted the deprecated `browser.user_agent`; it now emits
`user_agent.original`. BREAKING for any customer query or dashboard keyed on
`browser.user_agent` — nothing in our own read path reads it (session replay
reads a `UserAgent` column, `inspect-trace` already lists the semconv key).
@Makisuo Makisuo changed the title fix(warehouse): resolve the deployment environment from either semconv key fix(warehouse): read renamed OTel attributes under either semconv spelling Aug 21, 2026
@Makisuo
Makisuo merged commit c469fc3 into main Aug 21, 2026
73 of 75 checks passed
@Makisuo
Makisuo deleted the fix/deployment-environment-name-dual-key branch August 21, 2026 14:41
@Makisuo
Makisuo deployed to pr-preview August 21, 2026 14:41 — with GitHub Actions Active
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Note

Preview resources were removed when this pull request closed.

Final commit e986ca3 · View workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant