ADR-005 · Redis cache layer + rate limiting
Status: Accepted (cache + permission caching live; rate-limiting redesign in progress)
Context
The API does the same expensive work repeatedly — most sharply, resolving a user’s permissions on every authorized request (a multi-join walk of User → UserRole → Role → RolePermission). At the same time, auth endpoints need protection against brute-force and abuse. Both concerns want a fast, shared, out-of-process store — and one is already present for Celery.
Decision
Use Redis (already the Celery broker) as a cache layer and rate-limit backend:
- A cache module (
core/cache.py) provides the shared caching primitives. - User permissions are cached, so the hot authorization path avoids re-walking the RBAC joins on every request.
- Auth endpoints are rate-limited (Flask-Limiter): e.g. 5 failed logins/min/account, tighter limits on sensitive routes; account lockout after repeated failures.
Caching must respect tenancy: cache keys and invalidation are scoped so one tenant’s cached data can never serve another (consistent with the multi-tenancy rules).
Consequences
Positive
- Big win on the authorization hot path — permission checks get cheap.
- Reuses existing Redis; no new infrastructure.
- Rate limiting gives real brute-force protection on auth.
Negative / cost
- Cache invalidation becomes a correctness concern — a role/permission change must evict the cached permissions, or a user sees stale access.
- Tenancy discipline extends to cache keys; a sloppy key is a cross-tenant leak.
- The rate-limiting design is still being reworked (the current shape is a first pass); config caching is queued next.
Alternatives considered
- No caching, recompute per request — simplest and always-correct, but wasteful on the busiest path.
- In-process memory cache — fast but not shared across workers/instances and lost on restart; Redis is shared and already present.