Skip to content

feat: smart caching strategy for dashboard & analytics (#127)#396

Open
sinatragianpaolo-oc wants to merge 2 commits intorohitdash08:mainfrom
sinatragianpaolo-oc:feat/smart-caching
Open

feat: smart caching strategy for dashboard & analytics (#127)#396
sinatragianpaolo-oc wants to merge 2 commits intorohitdash08:mainfrom
sinatragianpaolo-oc:feat/smart-caching

Conversation

@sinatragianpaolo-oc
Copy link

@sinatragianpaolo-oc sinatragianpaolo-oc commented Mar 14, 2026

Adds smart caching strategy for dashboard & analytics to fix #127.

Tiered TTLs (STATIC/ANALYTICS/DASHBOARD/AI_RESULT/SHORT), cached() route decorator, explicit invalidation on mutations, flask cache-stats endpoint. Graceful degradation: cache errors never crash routes.


Review fixes (latest commit)

  1. BUG: get_cache_stats used bytes keyshgetall returns str keys when Redis is initialised with decode_responses=True (as it is here). Using b'hits' / b'misses' always fell through to the default 0, making counters permanently zero. Fixed to raw.get('hits', 0) / raw.get('misses', 0) with an inline comment documenting the invariant.

  2. cached() decorator: None from get_json() now skippedresult.get_json(force=True) returns None when the response body is not valid JSON (empty body, non-JSON content type, etc.). Storing None in Redis would poison the key: every subsequent cache hit would return a null JSON response instead of calling the real handler. Added if data is None: return result guard before cache_set, with explanatory comment.

  3. _increment_hit / _increment_miss now log on Redis failure — previously except: pass swallowed all errors silently, making Redis connectivity issues invisible. Changed to except Exception as exc: logger.debug(...) — failures appear in debug logs without adding production noise.

  4. /insights/cache-stats access policy documented — the endpoint was open to all authenticated users with no admin guard. Added an explicit docstring on both GET and DELETE explaining the deliberate decision: the exposed data (hit/miss counters, Redis memory) contains no PII, so any authenticated user may consult it. The comment also shows the snippet needed to add an admin check if the deployment requires it.

New tests (5):

  • test_increment_hit_redis_failure_logged / test_increment_miss_redis_failure_logged — caplog asserts the debug message is emitted
  • test_get_cache_stats_reads_string_keys — mock hgetall with str keys, asserts correct hit/miss values
  • test_get_cache_stats_bytes_keys_would_be_zero — regression proof that bytes keys are not used
  • test_cached_decorator_skips_none_json — asserts cache_set is never called when get_json() returns None

Closes #127

- Tiered TTL constants: STATIC(1h) > AI_RESULT(30m) > ANALYTICS(10m)
  > DASHBOARD(5m) > SHORT(1m)
- cache() decorator for route-level caching
- AI budget suggestions cached 30min (expensive Gemini calls)
- Categories cached 1h, invalidated on create/update/delete
- GET /insights/cache-stats — hit/miss monitoring endpoint
- DELETE /insights/cache-stats — reset counters
- Graceful degradation: Redis errors never crash routes
- 20 tests covering operations, TTL, graceful degradation, HTTP

Closes rohitdash08#127
…ent cache-stats access

Fix 1 — get_cache_stats used bytes keys (b'hits') instead of str ('hits')
  Redis is initialised with decode_responses=True, so hgetall returns str
  keys.  Using b'hits' always fell back to the default 0, making hit/miss
  counters permanently zero.  Changed to raw.get('hits', 0) and added an
  inline comment explaining the invariant.

Fix 2 — cached() now guards against None from get_json()
  result.get_json(force=True) returns None when the response body is not
  valid JSON.  Storing None in Redis would poison the key: every subsequent
  cache hit would return null JSON instead of calling the real handler.
  Added: if data is None: return result before cache_set, with comment.

Fix 3 — _increment_hit / _increment_miss now log on Redis failure
  Previously bare 'except: pass' swallowed all errors silently, making it
  impossible to diagnose Redis connectivity issues via logs.  Changed to
  'except Exception as exc: logger.debug(...)' so failures appear in debug
  logs without affecting production noise level.

Fix 4 — /insights/cache-stats access policy documented
  The route was open to all authenticated users without an admin guard,
  while issue description mentioned /reminders/job-runs has one.  Added
  explicit docstring comment documenting the deliberate decision: the data
  exposed (hit/miss counters, Redis memory) contains no PII; any authenticated
  user may consult it.  Comment includes snippet for adding an admin check.

Tests added:
  - test_increment_hit_redis_failure_logged (caplog, fix 3)
  - test_increment_miss_redis_failure_logged (caplog, fix 3)
  - test_get_cache_stats_reads_string_keys (fix 1)
  - test_get_cache_stats_bytes_keys_would_be_zero (fix 1 regression proof)
  - test_cached_decorator_skips_none_json (fix 2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Smart caching strategy for dashboard & analytics

1 participant