Skip to content

feat(api): add opt-in spec.ingest.tenant write routing - #20

Draft
tdakkota wants to merge 2 commits into
claude/policy-ecfrom
claude/ingest-tenant
Draft

feat(api): add opt-in spec.ingest.tenant write routing#20
tdakkota wants to merge 2 commits into
claude/policy-ecfrom
claude/ingest-tenant

Conversation

@tdakkota

Copy link
Copy Markdown
Contributor

Adds spec.ingest.tenant, mirroring the opt-in tenant: block odbingest gained in
oteldb#1280{header, resourceAttributes, default, require}, composing narrowest-first (header ▸ resource attribute ▸ default).

spec:
  ingest:
    tenant:
      header: X-Scope-OrgID
      resourceAttributes: [service.namespace]
      default: shared
      require: false

It is off by default, and that is the point

oteldb's read path is still pinned to a single tenant. storagebackend.Backend.tenant is never
assigned — there is no option or setter for it — so it stays "" and normalizes to "default" on
every read; every PromQL, LogQL, TraceQL and Pyroscope query resolves that one tenant
(oteldb#820).

So enabling ingest-side tenancy today routes writes into tenants nothing can query. The data is
stored, under a different shard key, and is invisible until the read path catches up. The field
needs to exist so the deployment is configurable ahead of that — but the default must stay today's
behaviour, and the godoc on IngestSpec.Tenant and TenantSpec says so explicitly, as does the
README's new Ingest tenancy section.

Absent renders no tenant block at all, byte-identical to what an ingest pool got before this
field existed: odbingest installs no resolver and every write lands in "default".

Validation

Each check mirrors cmd/odbingest's newTenantResolver / parseTenantID, because odbingest fails
these at startup — a Degraded/InvalidSpec condition naming the field is a better report than a
crash-looping Deployment:

  • require without header — there is no header to require.
  • an empty entry in resourceAttributes.
  • a default that is not a legal tenant id (charset, 150-byte bound, . / ..) — the id becomes a
    backend path segment and an etcd key component.
  • a block naming no source at all: odbingest builds no resolver for it, so the stanza would be
    present and inert.

tenant also joins spec.ingest.extraConfig's reserved paths, since the block is now modelled in
full.

Stack

Based on #19 (spec.policy.ec) → #17 (spec.cluster.privateBackend) → #15 (spec.query) → #14
(spec.ingest). The dependency on #14 is real — IngestSpec is where the field lives; the rest is
stack ordering.

Mirrors odbingest's tenant block (header, resource_attributes, default, require) onto the
CRD, and validates what odbingest would otherwise crash-loop on at startup.

It stays off by default, and the field docs say why: oteldb's read path is still pinned to
one tenant — the query Backend's tenant id is never assigned, so every query resolves
"default" (oteldb#820). Enabling ingest-side tenancy today routes writes into tenants
nothing can query. Absent renders no tenant block at all, byte-identical to today.
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