perf(round2): Redis route cache + distributed cache-fill lease + distributed gateway limiter - #12
Merged
Conversation
… distributed gateway limiter Second scalability round (ADR-0005). Preserves all business/security/idempotency/state-machine guarantees; everything new is optional and config-reversible. ./scripts/verify.sh green, including real-Redis Testcontainers integration tests. - Two Redis roles as two instances (never logical DBs): the existing STATE Redis stays `noeviction` and now also holds the distributed rate-limit counters; a new optional, disposable CACHE Redis (docker-compose.cache.yml: allkeys-lfu, no persistence, port 6380) holds only derived data. Off by default so the low-mem prod host is unaffected. - map-service Redis route read-cache (cache-aside over the MySQL snapshot): versioned String key cache:map:route:v1:<sha256>, TTL = base+jitter capped by the snapshot's remaining freshness, oversize/decode protection, endpoints never cached, every Redis failure degrades to a miss and can never mask credential/city/coordinate/provider errors or fabricate a route. - Distributed cache-fill lease (cache Redis): SET NX PX + atomic owner-compare-and-delete Lua, bounded lease + bounded loser wait; cache-fill dedup only, never business correctness. - Distributed gateway limiter: RATE_LIMIT_BACKEND=redis now works via reactive Lua on the state Redis (no blocking call on the Netty event loop); Retry-After = real window remainder; misconfig fails startup instead of silently reverting to memory; Redis outage -> metered degraded mode (bounded local emergency, or fail-closed for sensitive buckets), never silently unlimited. - Tests: unit (TTL jitter/cap) + Testcontainers (cache put/get, malformed-repair, oversize, lease exclusivity/owner-release/expiry, two map instances -> one provider call, two limiter instances -> one shared quota, concurrency, window reset, Retry-After, degraded mode). disabledWithoutDocker so mvn test stays green without a daemon. - Docs: ADR-0005, AGENTS.md (S49 corrected to merged, S50 added), architecture/operations/ load-testing/security, .env.example, compose overlay. Load evidence (multi-instance provider-call/hit-rate/p95/Redis-memory/429 comparison) needs a non-prod staging target with adequate hardware and is not captured here; the distributed behavior is proven by the Testcontainers tests. Production was never touched. Co-Authored-By: Claude Opus 4.8 <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 & why
Second performance/scalability round on top of the merged PR #11 (S49). Implements the already-approved architecture (see
docs/adr/0005): a real Redis read cache with avalanche/stampede protection, a narrowly-scoped distributed cache-fill lease, and a genuinely distributed gateway rate limiter — preserving every business, security, idempotency, and state-machine guarantee. Everything new is optional and config-reversible, and adds zero footprint to the low-memory prod demo host by default../scripts/verify.shis green: all 15 backend modules (~386 tests, including live-Redis Testcontainers integration tests), both frontends typecheck + build.Changes (6 slices)
noeviction) now also holds the distributed rate-limit counters. A new optional, disposable Cache Redis — separate instance viadocker-compose.cache.yml(allkeys-lfu, no persistence, port 6380) — holds only derived data (route cache + leases). Off by default. Instance-level isolation, never logical DBs.cache:map:route:v1:<sha256>String; TTL = base + jitter capped by the snapshot's remaining freshness; oversize/decode protection; endpoints never cached; every Redis failure degrades to a miss and can never mask credential/city/coordinate/provider errors or fabricate a route.SET NX PX+ atomic owner-compare-and-delete Lua, bounded lease + bounded loser wait. Cache-fill dedup only — never seat/order/payment/identity/authz correctness.RATE_LIMIT_BACKEND=redisnow works — reactive Lua on the state Redis, no blocking Redis call on the Netty event loop,Retry-After= real window remainder. Misconfig fails startup (never silent-memory). Redis outage → metered degraded mode (bounded local emergency, or fail-closed for sensitive buckets), never silently unlimited. In-memory retained for single-instance demo.Retry-After, degraded mode.@Testcontainers(disabledWithoutDocker=true)keepsmvn testgreen without a daemon.AGENTS.md(S49 corrected to merged6376e30, S50 added); architecture/operations/load-testing/security;.env.example; compose overlay.Verification (separated, per the brief)
verify.shgreen; the distributed behaviors are proven by real-Redis Testcontainers tests, not mocks.docker compose -f docker-compose.yml -f docker-compose.cache.yml configvalidates; cache Redis is a separateallkeys-lfuinstance on 6380.route_snapshots-read counts; hit rate; p50/p95/p99; Redis memory; 429 curves) needs a non-prod staging target with adequate hardware this session didn't have.scripts/loadtest/*are ready and production-denylisted. Production domain was never touched.Rollback
Pure config:
MAP_ROUTE_CACHE_REDIS_ENABLED=false→ MySQL/provider path;RATE_LIMIT_BACKEND=memory→ single-gateway limiter; dropping the cache overlay doesn't stop the demo stack. No DB rollback.🤖 Generated with Claude Code