Skip to content

feat(redis): tier-aware backend routing behind a default-off flag#54

Merged
mastermanas805 merged 1 commit into
masterfrom
feat/redis-tier-aware-routing
Jun 8, 2026
Merged

feat(redis): tier-aware backend routing behind a default-off flag#54
mastermanas805 merged 1 commit into
masterfrom
feat/redis-tier-aware-routing

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

What

Adds tier-aware Redis backend routing so non-Team tiers can be served from a shared ACL carve (one pod, many tenants) instead of a dedicated pod per token. Gated behind a new flag, default OFF / fail-closed — a strict no-op in prod until an operator opts in.

Why

The shared redisBackend is built from REDIS_PROVISION_BACKEND (= k8s in prod), so the K8sBackend (one Redis pod + namespace + PVC + Service + NetworkPolicy per token) serves every non-dedicated tier — anonymous, free, hobby, hobby_plus. A 5MB anonymous cache therefore costs a whole pod. Pod count grows linearly with /cache/new; it does not scale. Dedicated pods should be a Team-only privilege.

Design

New redis.TierDispatchBackend wraps two backends and routes:

Call Routing signal Team non-Team
Provision tier dedicated pod (configured backend, k8s) shared ACL carve (LocalBackend)
Deprovision / StorageBytes provider_resource_id prefix (instant-customer-) dedicated shared carve
Regrade PRID prefix forward to dedicated Regrader soft-skip
  • The PRID-prefix routing reuses the exact instant-customer- convention server.go already uses to split dedicated vs shared teardown — Deprovision/StorageBytes don't carry the tier, so the PRID is the only reliable signal.
  • Regrade only ever touches dedicated (k8s) pods. A shared-carve resource soft-skips so the shared pod's server-wide maxmemory is never set on behalf of one tenant (that would cap every co-tenant). The dispatcher itself implements redis.Regrader, so the server's existing s.redisBackend.(redis.Regrader) assertion still succeeds.
  • dedicatedTier and dedicatedNamespacePrefix are named constants; the prefix is an alias of redisK8sNsPrefix so it can never drift from what the k8s backend actually stamps.

Flag

REDIS_TIER_AWARE_ROUTING_ENABLEDconfig.Config.RedisTierAwareRoutingEnabled, default false. Only the exact string "true" enables it (same pattern as K8S_DEDICATED_BACKEND); any other value is off.

NO-OP guarantee

When the flag is off/unset, server.New wires the configured backend verbatim — the diff is literally redis.NewBackend(...) -> a local var of the same expression, wrapped only inside if cfg.RedisTierAwareRoutingEnabled. Behaviour is byte-for-byte identical to today. No data migration, no prod config/secret change, no default-backend change.

Scope / review note

Code-only, flag-gated, safe to merge as a no-op. Do not enable in prod without review: when on, non-Team tiers lose the per-pod maxmemory hard cap (Redis ACLs have no per-user memory lever — maxmemory is server-wide). Memory is then enforced out-of-band via the worker's StorageBytes quota scan — the same posture the default local backend already runs with. Isolation (ACL key-prefix + command allowlist) is unchanged and fully enforced by LocalBackend.

Tests

  • internal/backend/redis/dispatch_test.go — Provision (registry-driven tier table, rule 18), Deprovision/StorageBytes (PRID routing), Regrade (forward / shared soft-skip / no-Regrader soft-skip / error propagation), interface conformance.
  • internal/server/server_redis_tier_routing_test.go — flag off -> backend unwrapped (no-op proof), default -> unwrapped, flag on -> wrapped.
  • internal/config/config_test.go — default false, override true, non-"true"-is-false.
  • New code 100% covered; make gate green; golangci-lint clean.

🤖 Generated with Claude Code

The shared redis backend (REDIS_PROVISION_BACKEND, "k8s" in prod) serves
every non-dedicated tier, so a 5MB anonymous cache costs a whole pod, PVC,
Service and NetworkPolicy — pod count grows linearly with /cache/new. This
is the durable fix for that capacity problem.

Adds redis.TierDispatchBackend: routes Provision by tier (Team -> dedicated
pod, every non-Team tier -> a shared ACL carve on one pod), and routes
Deprovision/StorageBytes/Regrade by provider_resource_id prefix (the same
"instant-customer-" convention server.go already uses). Regrade only ever
touches dedicated (k8s) pods; a shared-carve resource soft-skips so the
shared pod's server-wide maxmemory is never set on behalf of one tenant.

Gated by REDIS_TIER_AWARE_ROUTING_ENABLED (default false, fail-closed: only
the exact string "true" enables it). When off or unset, server.New wires the
configured backend verbatim — behaviour is byte-for-byte identical to today.
This is a code-only change: no data migration, no prod config/secret change,
no default-backend change. Safe to merge as a no-op until an operator opts in
(needs careful review before any enablement; non-Team tiers lose the
per-pod maxmemory hard cap when on — memory is then enforced out-of-band via
the worker's StorageBytes quota scan, the same posture as the "local" backend).

Tests: dispatch_test.go covers Provision (registry-driven tier table),
Deprovision/StorageBytes (PRID routing), and Regrade (forward / shared
soft-skip / no-Regrader soft-skip). server_redis_tier_routing_test.go proves
flag off -> backend unwrapped (no-op) and flag on -> wrapped. config_test.go
covers default-false, override-true, and non-"true"-is-false. New code 100%
covered; make gate green; golangci-lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit 0c95ba9 into master Jun 8, 2026
12 checks passed
@mastermanas805 mastermanas805 deleted the feat/redis-tier-aware-routing branch June 8, 2026 18:22
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