feat: per-tenant BackendStorageRouting (part 1/2) - #107
Merged
Merged
Conversation
Scope the per-metric storage-backend routing table per tenant — follow-up to PR #333 to unblock multi-tenant deploys. * `BackendStorageRouting` carries a `tenant: String` field (default `"default"` for single-tenant deploys / existing call sites). YAML + JSON parsers pick the field up; absence resolves to the default tenant so existing configs are byte-compatible. * `HotReloadBackendStorageRouting` is now a per-tenant map internally (`HashMap<TenantId, Arc<BackendStorageRouting>>` behind a single `ArcSwap`). New `swap_tenant(tenant, table)` / `snapshot_for_tenant(tenant)` ops; the legacy `swap()` / `snapshot()` route to the `default` tenant for back-compat. * HTTP query handler reads `X-ASAP-Tenant` header (default `"default"`); lookup picks that tenant's table, falls back to the `default` tenant when the requested tenant has no entry. * `POST /api/v1/storage_routing` swap is per-tenant — body `tenant` field wins, header is the fallback signal, missing- both falls back to `default`. `GET /api/v1/storage_routing` reports the tenant inferred from the header plus a `tenants` fleet-listing for diagnostics. * Unit + HTTP-integration tests cover the lookup, fallback, isolation, and back-compat paths. Out of scope (deliberate): * Tenant-aware AUTH — `X-ASAP-Tenant` is unauthenticated for MVP. Anyone can pick any tenant by setting the header. * Sketch state isolation — sketches are still global; only the routing table is tenant-scoped. Pairs with ASAPCollector PR (per-tenant emit) — see #46. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to PR #333 (issue #46): scope the per-metric
BackendStorageRoutingtable per tenant so multi-tenantdeploys can isolate each tenant's routing decisions while sharing
the warm sketch tier and archive tier infrastructure.
This is part 1/2 — the data model + backend lookup + hot-reload
swap. Part 2 lands in ASAPCollector (
feat: per-tenant BackendStorageRouting emit (part 2/2)) and threads the tenantthrough the controller's emit path.
Data model decision
Hybrid:
BackendStorageRoutingcarries atenant: Stringfield(default
"default"), andHotReloadBackendStorageRoutingwraps a
HashMap<TenantId, Arc<BackendStorageRouting>>behinda single
ArcSwap. The wrapper is the multi-tenant indirection;the table itself is tenant-scoped.
This minimises blast radius:
snapshot()/swap(); both route to thedefaulttenant.snapshot_for_tenant(tenant)/swap_tenant(tenant, table)— per-tenant pushes don't touchother tenants.
tenant:field) keep parsingas
default-tenant tables.Backend lookup behaviour
X-ASAP-Tenantheader. Missing /empty / non-UTF-8 →
default.resolve_metric_storagesnapshots that tenant's table.Unknown-tenant → falls back internally to the
defaulttenant's table (one-level fallback chain).
process_query_requestdispatch path only changes itsrouting-table read; the rest (engine override, EngineRouter
dispatch) is untouched.
Hot-reload semantics
POST /api/v1/storage_routing:tenantfield is the source of truth.X-ASAP-Tenantheader is the fallback signal when the bodyleaves
tenantimplicit (preserves existing controller's emitshape; the controller-side patch in part 2 makes it explicit).
defaulttenant slot.GET /api/v1/storage_routing:tenants: [...]fleet-listing of every registeredtenant id for operator diagnostics.
Test coverage
Unit (in
routing::backend_storage_routing::tests):empty_routing_carries_default_tenantjson_payload_tenant_field_is_optional_and_defaults_to_defaultjson_payload_tenant_field_is_picked_up_when_presentyaml_tenant_field_is_optional_and_defaults_to_defaultyaml_tenant_field_is_picked_up_when_presenthot_reload_swap_tenant_replaces_only_one_tenanthot_reload_unknown_tenant_falls_back_to_default_tenanthot_reload_swap_tenant_overwrites_table_internal_tenant_idhot_reload_tenant_ids_lists_all_registered_tenantshot_reload_legacy_swap_routes_to_default_tenantHTTP integration (in
drivers::query::servers::http::tests):storage_routing_post_per_tenant_isolates_tenants— body'stenantlands in the right slot; default tenant unchanged.storage_routing_post_per_tenant_via_header_when_body_implicit— header fallback when controller emits tenant-agnostic body.
storage_routing_post_no_tenant_falls_back_to_default— back-compat with existing controllers.
storage_routing_get_per_tenant_lists_all_tenants.Existing single-tenant tests (the legacy
swap()/snapshot()suite, the v6.1 / v7 YAML parse + lookup suite, and the
storage_routing_*HTTP suite) still pass byte-for-byte.Out of scope (deliberate, MVP)
X-ASAP-Tenantis unauthenticated.Anyone can pick any tenant by setting the header. Tenant-aware
auth is deferred to a follow-up before any multi-tenant deploy
is considered production-ready.
the routing table is tenant-scoped. Per-tenant sketch state
is a much larger refactor (touches ingest, storage, and the
precompute engine).
X-ASAP-Tenantheaderand the JSON
tenantfield.Test plan
cargo build --releasepassescargo test --release --lib— all routing + HTTP storage-routing tests green (54/54 storage-routing; 10 datafusion
failures are pre-existing on
origin/main)Related
🤖 Generated with Claude Code