feat(redis): tier-aware backend routing behind a default-off flag#54
Merged
Conversation
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>
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.
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
redisBackendis built fromREDIS_PROVISION_BACKEND(=k8sin 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.TierDispatchBackendwraps two backends and routes:ProvisiontierLocalBackend)Deprovision/StorageBytesprovider_resource_idprefix (instant-customer-)RegradeRegraderinstant-customer-conventionserver.goalready uses to split dedicated vs shared teardown —Deprovision/StorageBytesdon't carry the tier, so the PRID is the only reliable signal.Regradeonly ever touches dedicated (k8s) pods. A shared-carve resource soft-skips so the shared pod's server-widemaxmemoryis never set on behalf of one tenant (that would cap every co-tenant). The dispatcher itself implementsredis.Regrader, so the server's existings.redisBackend.(redis.Regrader)assertion still succeeds.dedicatedTieranddedicatedNamespacePrefixare named constants; the prefix is an alias ofredisK8sNsPrefixso it can never drift from what the k8s backend actually stamps.Flag
REDIS_TIER_AWARE_ROUTING_ENABLED—config.Config.RedisTierAwareRoutingEnabled, defaultfalse. Only the exact string"true"enables it (same pattern asK8S_DEDICATED_BACKEND); any other value is off.NO-OP guarantee
When the flag is off/unset,
server.Newwires the configured backend verbatim — the diff is literallyredis.NewBackend(...)-> a local var of the same expression, wrapped only insideif 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
maxmemoryhard cap (Redis ACLs have no per-user memory lever —maxmemoryis server-wide). Memory is then enforced out-of-band via the worker'sStorageBytesquota scan — the same posture the defaultlocalbackend already runs with. Isolation (ACL key-prefix + command allowlist) is unchanged and fully enforced byLocalBackend.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.make gategreen;golangci-lintclean.🤖 Generated with Claude Code