Problem
Several read paths recompute or re-fetch identical data many times within the lifetime of a single worker process. There is no in-process caching today beyond the ad-hoc stat_cache dict in backend/routes/profile.py:491.
Hot candidates:
backend/services/graph_service.py — graph reads invoked by the streaming tutor (routes/learn.py) on every chat turn.
backend/services/course_context_service.py:66 — get_course_context() is called frequently and returns DB-aggregated data that changes only on graph updates.
- Auth/JWT decoding paths in
backend/services/auth_guard.py — same token decoded repeatedly across a session.
Proposal
Use functools.lru_cache (or a small TTL-LRU helper) for deterministic, per-process reads where staleness is acceptable for short windows or where we have a clear invalidation hook.
Targets:
- Graph node/edge fetch helpers in
graph_service.py — invalidated explicitly in apply_graph_update (line 375).
get_course_context(course_id) — invalidated after the existing update_course_context background task runs.
- Token decode /
require_self claim parsing — TTL bounded to token lifetime.
Acceptance criteria
Tradeoffs / risks
- Per-worker only — does not dedupe across uvicorn workers or Docker replicas. That's the Redis issue's job. These two layers compose.
- Risk of stale reads if invalidation hooks are missed. Mitigation: keep the surface area small and only cache functions whose mutators are already centralized (graph mutations all go through
apply_graph_update per CLAUDE.md).
lru_cache keys must be hashable — watch for dict/list args.
Related
Problem
Several read paths recompute or re-fetch identical data many times within the lifetime of a single worker process. There is no in-process caching today beyond the ad-hoc
stat_cachedict inbackend/routes/profile.py:491.Hot candidates:
backend/services/graph_service.py— graph reads invoked by the streaming tutor (routes/learn.py) on every chat turn.backend/services/course_context_service.py:66—get_course_context()is called frequently and returns DB-aggregated data that changes only on graph updates.backend/services/auth_guard.py— same token decoded repeatedly across a session.Proposal
Use
functools.lru_cache(or a small TTL-LRU helper) for deterministic, per-process reads where staleness is acceptable for short windows or where we have a clear invalidation hook.Targets:
graph_service.py— invalidated explicitly inapply_graph_update(line 375).get_course_context(course_id)— invalidated after the existingupdate_course_contextbackground task runs.require_selfclaim parsing — TTL bounded to token lifetime.Acceptance criteria
lru_cacheis safe (pure inputs, no hidden mutation).cache_clear()hook called fromapply_graph_updateand from the document/grade post-roll background tasks already inroutes/documents.py:567,785,991.CLAUDE.md(Conventions section) thatlru_cacheis reserved for per-process reads and that any state-mutating function must call the matchingcache_clear.Tradeoffs / risks
apply_graph_updateperCLAUDE.md).lru_cachekeys must be hashable — watch for dict/list args.Related
Cache-Control+ETag.