Decisions (ADRs)ADR-005 · Caching & rate limiting

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.