Skip to content

mvp phase α: controller emits BackendStorageRouting via stage_config - #314

Merged
zzylol merged 1 commit into
mainfrom
mvp/phase-a-controller-routing-emit
May 7, 2026
Merged

zzylol merged 1 commit into
mainfrom
mvp/phase-a-controller-routing-emit

Conversation

@zzylol

@zzylol zzylol commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase α of the planner consolidation: the controller becomes the sole authority for BackendStorageRouting. Today the routing table is hand-authored YAML at deploy/configs/backend-storage-routing.yaml; after Phase α the controller emits it from its typed L5 stage_config and pushes it to the backend on every plan emit.

  • config::stage_config::emit_backend_storage_routing(&[(metric, &BackendStageConfig)]) — derives (metric, [target]) rows from the L4 sketch families landed at the backend. Classification rules:
    • DDSketch / KLL planned → warm tier serves quantile / quantile_over_time.
    • HLL planned → warm tier serves count (cardinality readout). count drops off the archive's claim list.
    • Count-Sketch planned → warm tier serves topk (heap-augmented Estimate). topk drops off the archive's claim list.
    • CMS planned → warm tier serves count (CMS Estimate readout).
    • Archive-eligible shapes — histogram_quantile, delta, deriv, absent, rate_post_hoc, plus topk / count when no matching sketch was planned.
    • Warm-tier slot stays the default (no applies_to_query_shape) — unanticipated shapes route to warm rather than falling through to an archive first-target fallback.
  • BackendClient::post_storage_routing_json — sibling of post_streaming_config_json. Rewrites the streaming-config URL's path to /api/v1/storage_routing so operators configure one CONTROLLER_BACKEND_ENDPOINT.
  • main::handle_plan — when USE_TYPED_STAGE_SPLIT=1, also emit + POST the routing JSON. Same fire-and-forget contract as the existing streaming-config push.

Test plan

  • cargo build clean
  • 11 new tests pass (7 emitter unit / snapshot tests, 2 URL-derivation tests, 2 mock-backend integration tests)
  • No regression in existing tests (493 pass, 10 fail — same 10 pre-existing failures as origin/main)
  • Snapshot test pins the exact JSON shape for a DDSketch + HLL + Count-Sketch plan

Schema example

The snapshot test produces (for three metrics, mix of sketch families):

```json
{
"default_engine": "sketch_warm_tier",
"metrics": [
{
"name": "http_requests_total",
"targets": [
{ "engine": "sketch_warm_tier" },
{
"engine": "thanos_archive",
"applies_to_query_shape": [
"histogram_quantile", "delta", "deriv",
"absent", "rate_post_hoc", "count"
]
}
],
"warm_tier_native_shapes": ["topk", "rate", "sum", "avg", "min", "max"]
}
]
}
```

Coordination

  • Behind `USE_TYPED_STAGE_SPLIT=1` flag — safe to merge ahead of the backend consumer (Part B, separate PR).
  • Phase β (asap-planner-rs pattern migration) does not conflict — Phase α only extends the existing emitter.
  • Auto-merge if green.

🤖 Generated with Claude Code

The controller's L1→L5 pipeline (PRs #273-279) plans sketch placement,
and Phase B (PR #297) added `emit_backend_config_json` for the backend's
StreamingConfig. The per-metric `BackendStorageRouting` table that the
backend's HTTP query handler consults on every PromQL query, however,
was still hand-authored YAML at `deploy/configs/backend-storage-routing.yaml`
— two sources of truth, drift between the controller's stage-split
decisions and the routing config, and manual edits required when the
workload changes.

Phase α makes the controller THE planner: it emits a
`BackendStorageRouting` JSON document as part of every plan emit, the
backend hot-loads it on push, and routing flows via OpAMP push instead
of YAML edits.

Concretely:

* `config::stage_config::emit_backend_storage_routing(metric_plans)` —
  for each `(metric, &BackendStageConfig)` pair the controller has
  planned this cycle, emit a `metrics:` row with the per-shape engine
  routing list. Classification rules are sourced from the L4
  `sketch_algebra` outputs landing at the backend (DDSketch / KLL →
  warm tier for `quantile`; HLL → warm tier for `count`; Count-Sketch
  → warm tier for `topk`; CMS → warm tier for `count` / `point_count`).
  Archive-eligible shapes — `histogram_quantile`, `delta`, `deriv`,
  `absent`, `rate_post_hoc`, plus `topk` / `count` when no sketch
  claims them — are emitted on a `thanos_archive` target with an
  explicit `applies_to_query_shape` filter. Warm-tier slot stays the
  default (no filter) so unanticipated shapes route to warm rather
  than failing through to the archive's first-target fallback.

* `BackendClient::post_storage_routing_json` — sibling of
  `post_streaming_config_json`. Rewrites the configured streaming-config
  endpoint URL's path component from `/api/v1/streaming-config` to
  `/api/v1/storage_routing` so operators only configure one
  `CONTROLLER_BACKEND_ENDPOINT` and both pushes land at the same backend
  host.

* `main::handle_plan` — when `USE_TYPED_STAGE_SPLIT=1` and the typed L5
  emitter produced a `BackendStageConfig`, also call
  `emit_backend_storage_routing` and POST it via the shared
  `BackendClient`. Same fire-and-forget contract as the existing
  `streaming-config` push: errors logged at WARN, the next replan cycle
  retries.

Tests: 11 new tests (7 emitter unit / snapshot tests, 2 URL-derivation
tests, 2 mock-backend integration tests). Snapshot test in
`storage_routing_three_metric_snapshot_stable` pins the exact JSON
shape for a DDSketch + HLL + Count-Sketch plan so accidental schema
drift surfaces immediately. Pre-existing 10 controller failures
unchanged (493 pass, was 482+).

Phase α is gated behind `USE_TYPED_STAGE_SPLIT=1` (the existing typed
L5 path). Operators can still hand-author
`deploy/configs/backend-storage-routing.yaml` for dev / standalone
deployments — the backend falls back to the static YAML when no JSON
has been pushed yet (Part B, separate PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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